Merge "Move deduceRestrictedCapability to libs/net and rename it" into sc-dev
diff --git a/Android.bp b/Android.bp
index ee5e992..f47ee20 100644
--- a/Android.bp
+++ b/Android.bp
@@ -401,6 +401,13 @@
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.hardware.security.keymint-V1-java-source",
+ ":android.hardware.security.secureclock-V1-java-source",
+ ":android.security.apc-java-source",
+ ":android.security.authorization-java-source",
+ ":android.security.maintenance-java-source",
+ ":android.security.vpnprofilestore-java-source",
+ ":android.system.keystore2-V1-java-source",
":credstore_aidl",
":dumpstate_aidl",
":framework_native_aidl",
@@ -582,13 +589,7 @@
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.vibrator-V2-java",
- "android.security.apc-java",
- "android.security.authorization-java",
- "android.security.maintenance-java",
- "android.security.vpnprofilestore-java",
- "android.system.keystore2-V1-java",
"android.system.suspend.control.internal-java",
- "cameraprotosnano",
"devicepolicyprotosnano",
"com.android.sysprop.apex",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
index 999860f..e65abcf 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
@@ -48,7 +48,8 @@
@Override
public boolean onStopJob(final JobParameters params) {
Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId()
- + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason()));
+ + ", reason="
+ + JobParameters.getLegacyReasonCodeDescription(params.getLegacyStopReason()));
return false;
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 4c8ab93..baec0c3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1021,6 +1021,41 @@
mJobId = jobId;
}
+ /**
+ * Creates a new Builder of JobInfo from an existing instance.
+ * @hide
+ */
+ public Builder(@NonNull JobInfo job) {
+ mJobId = job.getId();
+ mJobService = job.getService();
+ mExtras = job.getExtras();
+ mTransientExtras = job.getTransientExtras();
+ mClipData = job.getClipData();
+ mClipGrantFlags = job.getClipGrantFlags();
+ mPriority = job.getPriority();
+ mFlags = job.getFlags();
+ mConstraintFlags = job.getConstraintFlags();
+ mNetworkRequest = job.getRequiredNetwork();
+ mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+ mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
+ mTriggerContentUris = job.getTriggerContentUris() != null
+ ? new ArrayList<>(Arrays.asList(job.getTriggerContentUris())) : null;
+ mTriggerContentUpdateDelay = job.getTriggerContentUpdateDelay();
+ mTriggerContentMaxDelay = job.getTriggerContentMaxDelay();
+ mIsPersisted = job.isPersisted();
+ mMinLatencyMillis = job.getMinLatencyMillis();
+ mMaxExecutionDelayMillis = job.getMaxExecutionDelayMillis();
+ mIsPeriodic = job.isPeriodic();
+ mHasEarlyConstraint = job.hasEarlyConstraint();
+ mHasLateConstraint = job.hasLateConstraint();
+ mIntervalMillis = job.getIntervalMillis();
+ mFlexMillis = job.getFlexMillis();
+ mInitialBackoffMillis = job.getInitialBackoffMillis();
+ // mBackoffPolicySet isn't set but it's fine since this is copying from an already valid
+ // job.
+ mBackoffPolicy = job.getBackoffPolicy();
+ }
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Builder setPriority(int priority) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 0d3e001..60f6475 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -16,10 +16,14 @@
package android.app.job;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
+import android.content.pm.PackageManager;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.Uri;
@@ -30,6 +34,9 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Contains the parameters used to configure/identify your job. You do not create this object
* yourself, instead it is handed in to your application by the System.
@@ -82,7 +89,7 @@
*/
// TODO(142420609): make it @SystemApi for mainline
@NonNull
- public static String getReasonCodeDescription(int reasonCode) {
+ public static String getLegacyReasonCodeDescription(int reasonCode) {
switch (reasonCode) {
case REASON_CANCELED: return "canceled";
case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
@@ -96,12 +103,119 @@
}
/** @hide */
- // @SystemApi TODO make it a system api for mainline
+ // TODO: move current users of legacy reasons to new public reasons
@NonNull
public static int[] getJobStopReasonCodes() {
return JOB_STOP_REASON_CODES;
}
+ /**
+ * There is no reason the job is stopped. This is the value returned from the JobParameters
+ * object passed to {@link JobService#onStartJob(JobParameters)}.
+ */
+ public static final int STOP_REASON_UNDEFINED = 0;
+ /**
+ * The job was cancelled directly by the app, either by calling
+ * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a
+ * new job with the same job ID.
+ */
+ public static final int STOP_REASON_CANCELLED_BY_APP = 1;
+ /** The job was stopped to run a higher priority job of the app. */
+ public static final int STOP_REASON_PREEMPT = 2;
+ /**
+ * The job used up its maximum execution time and timed out. Each individual job has a maximum
+ * execution time limit, regardless of how much total quota the app has. See the note on
+ * {@link JobScheduler} for the execution time limits.
+ */
+ public static final int STOP_REASON_TIMEOUT = 3;
+ /**
+ * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this
+ * job.
+ */
+ public static final int STOP_REASON_DEVICE_STATE = 4;
+ /**
+ * The requested battery-not-low constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5;
+ /**
+ * The requested charging constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresCharging(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_CHARGING = 6;
+ /**
+ * The requested connectivity constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7;
+ /**
+ * The requested idle constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+ /**
+ * The requested storage-not-low constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9;
+ /**
+ * The app has consumed all of its current quota. Each app is assigned a quota of how much
+ * it can run jobs within a certain time frame. The quota is informed, in part, by app standby
+ * buckets. Once an app has used up all of its quota, it won't be able to start jobs until
+ * quota is replenished, is changed, or is temporarily not applied.
+ *
+ * @see UsageStatsManager#getAppStandbyBucket()
+ */
+ public static final int STOP_REASON_QUOTA = 10;
+ /**
+ * The app is restricted from running in the background.
+ *
+ * @see ActivityManager#isBackgroundRestricted()
+ * @see PackageManager#isInstantApp()
+ */
+ public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11;
+ /**
+ * The current standby bucket requires that the job stop now.
+ *
+ * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+ */
+ public static final int STOP_REASON_APP_STANDBY = 12;
+ /**
+ * The user stopped the job. This can happen either through force-stop, or via adb shell
+ * commands.
+ */
+ public static final int STOP_REASON_USER = 13;
+ /** The system is doing some processing that requires stopping this job. */
+ public static final int STOP_REASON_SYSTEM_PROCESSING = 14;
+
+ /** @hide */
+ @IntDef(prefix = {"STOP_REASON_"}, value = {
+ STOP_REASON_UNDEFINED,
+ STOP_REASON_CANCELLED_BY_APP,
+ STOP_REASON_PREEMPT,
+ STOP_REASON_TIMEOUT,
+ STOP_REASON_DEVICE_STATE,
+ STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+ STOP_REASON_CONSTRAINT_CHARGING,
+ STOP_REASON_CONSTRAINT_CONNECTIVITY,
+ STOP_REASON_CONSTRAINT_DEVICE_IDLE,
+ STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+ STOP_REASON_QUOTA,
+ STOP_REASON_BACKGROUND_RESTRICTION,
+ STOP_REASON_APP_STANDBY,
+ STOP_REASON_USER,
+ STOP_REASON_SYSTEM_PROCESSING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StopReason {
+ }
+
@UnsupportedAppUsage
private final int jobId;
private final PersistableBundle extras;
@@ -116,7 +230,8 @@
private final String[] mTriggeredContentAuthorities;
private final Network network;
- private int stopReason; // Default value of stopReason is REASON_CANCELED
+ private int mStopReason = STOP_REASON_UNDEFINED;
+ private int mLegacyStopReason; // Default value of stopReason is REASON_CANCELED
private String debugStopReason; // Human readable stop reason for debugging.
/** @hide */
@@ -145,15 +260,23 @@
}
/**
- * Reason onStopJob() was called on this job.
- * @hide
+ * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
+ * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
+ * yet been called.
*/
+ @StopReason
public int getStopReason() {
- return stopReason;
+ return mStopReason;
+ }
+
+ /** @hide */
+ public int getLegacyStopReason() {
+ return mLegacyStopReason;
}
/**
* Reason onStopJob() was called on this job.
+ *
* @hide
*/
public String getDebugStopReason() {
@@ -368,13 +491,16 @@
} else {
network = null;
}
- stopReason = in.readInt();
+ mStopReason = in.readInt();
+ mLegacyStopReason = in.readInt();
debugStopReason = in.readString();
}
/** @hide */
- public void setStopReason(int reason, String debugStopReason) {
- stopReason = reason;
+ public void setStopReason(@StopReason int reason, int legacyStopReason,
+ String debugStopReason) {
+ mStopReason = reason;
+ mLegacyStopReason = legacyStopReason;
this.debugStopReason = debugStopReason;
}
@@ -406,7 +532,8 @@
} else {
dest.writeInt(0);
}
- dest.writeInt(stopReason);
+ dest.writeInt(mStopReason);
+ dest.writeInt(mLegacyStopReason);
dest.writeString(debugStopReason);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 0f3d299..fa7a2d3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -139,19 +139,23 @@
* Once this method is called, you no longer need to call
* {@link #jobFinished(JobParameters, boolean)}.
*
- * <p>This will happen if the requirements specified at schedule time are no longer met. For
+ * <p>This may happen if the requirements specified at schedule time are no longer met. For
* example you may have requested WiFi with
* {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
* job was executing the user toggled WiFi. Another example is if you had specified
- * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
- * idle maintenance window. You are solely responsible for the behavior of your application
- * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+ * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left
+ * its idle maintenance window. There are many other reasons a job can be stopped early besides
+ * constraints no longer being satisfied. {@link JobParameters#getStopReason()} will return the
+ * reason this method was called. You are solely responsible for the behavior of your
+ * application upon receipt of this message; your app will likely start to misbehave if you
+ * ignore it.
* <p>
* Once this method returns (or times out), the system releases the wakelock that it is holding
* on behalf of the job.</p>
*
- * @param params The parameters identifying this job, as supplied to
- * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param params The parameters identifying this job, similar to what was supplied to the job in
+ * the {@link #onStartJob(JobParameters)} callback, but with the stop reason
+ * included.
* @return {@code true} to indicate to the JobManager whether you'd like to reschedule
* this job based on the retry criteria provided at job creation-time; or {@code false}
* to end the job entirely. Regardless of the value returned, your job must stop executing.
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 88f21a5..63b3959 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -40,9 +40,9 @@
/**
* Interface to access and modify the permanent and temporary power save allow list. The two lists
* are kept separately. Apps placed on the permanent allow list are only removed via an explicit
- * {@link #removeFromAllowList(String)} call. Apps allow-listed by default by the system cannot be
- * removed. Apps placed on the temporary allow list are removed from that allow list after a
- * predetermined amount of time.
+ * {@link #removeFromPermanentAllowList(String)} call. Apps allow-listed by default by the system
+ * cannot be removed. Apps placed on the temporary allow list are removed from that allow list after
+ * a predetermined amount of time.
*
* @hide
*/
@@ -402,9 +402,9 @@
*
* @param includingIdle Set to true if the app should be allow-listed from device idle as well
* as other power save restrictions
- * @hide
*/
@NonNull
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public int[] getAllowListedAppIds(boolean includingIdle) {
try {
if (includingIdle) {
@@ -445,7 +445,7 @@
* @param packageName The app to remove from the allow list
*/
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
- public void removeFromAllowList(@NonNull String packageName) {
+ public void removeFromPermanentAllowList(@NonNull String packageName) {
try {
mService.removePowerSaveWhitelistApp(packageName);
} catch (RemoteException e) {
@@ -463,8 +463,8 @@
*/
@UserHandleAware
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
- public void addToTemporaryAllowList(@NonNull String packageName, long durationMs,
- @ReasonCode int reasonCode, @Nullable String reason) {
+ public void addToTemporaryAllowList(@NonNull String packageName, @ReasonCode int reasonCode,
+ @Nullable String reason, long durationMs) {
try {
mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(),
reasonCode, reason);
@@ -488,7 +488,7 @@
@UserHandleAware
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public long addToTemporaryAllowListForEvent(@NonNull String packageName,
- @AllowListEvent int event, @ReasonCode int reasonCode, @Nullable String reason) {
+ @ReasonCode int reasonCode, @Nullable String reason, @AllowListEvent int event) {
try {
switch (event) {
case EVENT_MMS:
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index eba39c7..07231b0 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -439,12 +439,12 @@
* whitelisted by default by the system cannot be removed.
*
* @param packageName The app to remove from the whitelist
- * @deprecated Use {@link PowerExemptionManager#removeFromAllowList(String)} instead
+ * @deprecated Use {@link PowerExemptionManager#removeFromPermanentAllowList(String)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void removeFromWhitelist(@NonNull String packageName) {
- mPowerExemptionManager.removeFromAllowList(packageName);
+ mPowerExemptionManager.removeFromPermanentAllowList(packageName);
}
/**
@@ -455,13 +455,13 @@
* @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure.
* @param reason a optional human readable reason string, could be null or empty string.
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
- * String, long, int, String)} instead
+ * String, int, String, long)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public void whitelistAppTemporarily(@NonNull String packageName, long durationMs,
@ReasonCode int reasonCode, @Nullable String reason) {
- mPowerExemptionManager.addToTemporaryAllowList(packageName, durationMs, reasonCode, reason);
+ mPowerExemptionManager.addToTemporaryAllowList(packageName, reasonCode, reason, durationMs);
}
/**
@@ -470,13 +470,13 @@
* @param packageName The package to add to the temp whitelist
* @param durationMs How long to keep the app on the temp whitelist for (in milliseconds)
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
- * String, long, int, String)} instead
+ * String, int, String, long)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) {
mPowerExemptionManager.addToTemporaryAllowList(
- packageName, durationMs, REASON_UNKNOWN, packageName);
+ packageName, REASON_UNKNOWN, packageName, durationMs);
}
/**
@@ -490,14 +490,14 @@
* used for logging purposes. Could be null or empty string.
* @return The duration (in milliseconds) that the app is whitelisted for
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
- * String, int, int, String)} instead
+ * String, int, String, int)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
@WhitelistEvent int event, @Nullable String reason) {
return mPowerExemptionManager.addToTemporaryAllowListForEvent(
- packageName, event, REASON_UNKNOWN, reason);
+ packageName, REASON_UNKNOWN, reason, event);
}
/**
@@ -512,14 +512,14 @@
* used for logging purposes. Could be null or empty string.
* @return The duration (in milliseconds) that the app is whitelisted for
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
- * String, int, int, String)} instead
+ * String, int, String, int)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
@WhitelistEvent int event, @ReasonCode int reasonCode, @Nullable String reason) {
return mPowerExemptionManager.addToTemporaryAllowListForEvent(
- packageName, event, reasonCode, reason);
+ packageName, reasonCode, reason, event);
}
/**
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 7833a03..6ae91a0 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.util.proto.ProtoOutputStream;
import java.util.List;
@@ -36,7 +37,7 @@
/**
* Cancel the jobs for a given uid (e.g. when app data is cleared)
*/
- void cancelJobsForUid(int uid, String reason);
+ void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, String debugReason);
/**
* These are for activity manager to communicate to use what is currently performing backups.
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 b958c3f..325be1b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -266,6 +266,8 @@
String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];
+ int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
+
String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];
private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
@@ -505,6 +507,7 @@
int[] preferredUidForContext = mRecycledPreferredUidForContext;
int[] workTypeForContext = mRecycledWorkTypeForContext;
String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
+ int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext;
String[] shouldStopJobReason = mRecycledShouldStopJobReason;
updateCounterConfigLocked();
@@ -528,6 +531,7 @@
slotChanged[i] = false;
preferredUidForContext[i] = js.getPreferredUid();
preemptReasonForContext[i] = null;
+ preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED;
shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
}
if (DEBUG) {
@@ -551,6 +555,7 @@
int allWorkTypes = getJobWorkTypes(nextPending);
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
boolean startingJob = false;
+ int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
String preemptReason = null;
// TODO(141645789): rewrite this to look at empty contexts first so we don't
// unnecessarily preempt
@@ -582,6 +587,7 @@
// assign the new job to this context since we'll reassign when the
// preempted job finally stops.
preemptReason = reason;
+ preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
}
continue;
}
@@ -597,6 +603,7 @@
minPriorityForPreemption = jobPriority;
selectedContextId = j;
preemptReason = "higher priority job found";
+ preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
// In this case, we're just going to preempt a low priority job, we're not
// actually starting a job, so don't set startingJob.
}
@@ -604,6 +611,7 @@
if (selectedContextId != -1) {
contextIdToJobMap[selectedContextId] = nextPending;
slotChanged[selectedContextId] = true;
+ preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
preemptReasonForContext[selectedContextId] = preemptReason;
}
if (startingJob) {
@@ -631,8 +639,13 @@
}
// preferredUid will be set to uid of currently running job.
activeServices.get(i).cancelExecutingJobLocked(
+ preemptReasonCodeForContext[i],
JobParameters.REASON_PREEMPT, preemptReasonForContext[i]);
- preservePreferredUid = true;
+ // Only preserve the UID if we're preempting for the same UID. If we're stopping
+ // the job because something is pending (eg. EJs), then we shouldn't preserve
+ // the UID.
+ preservePreferredUid =
+ preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT;
} else {
final JobStatus pendingJob = contextIdToJobMap[i];
if (DEBUG) {
@@ -657,7 +670,8 @@
final JobStatus jobStatus = jsc.getRunningJobLocked();
if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_TIMEOUT, debugReason);
+ jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_TIMEOUT, debugReason);
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
index 6ffac91..02f9129 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
@@ -374,7 +374,7 @@
pw.print(pe.stopReasons.valueAt(k));
pw.print("x ");
pw.print(JobParameters
- .getReasonCodeDescription(pe.stopReasons.keyAt(k)));
+ .getLegacyReasonCodeDescription(pe.stopReasons.keyAt(k)));
}
pw.println();
}
@@ -621,7 +621,7 @@
if (reason != null) {
pw.print(mEventReasons[index]);
} else {
- pw.print(JobParameters.getReasonCodeDescription(
+ pw.print(JobParameters.getLegacyReasonCodeDescription(
(mEventCmds[index] & EVENT_STOP_REASON_MASK)
>> EVENT_STOP_REASON_SHIFT));
}
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 2b08ba5..a041f8c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -746,8 +746,11 @@
Slog.d(TAG, "Removing jobs for package " + pkgName
+ " in user " + userId);
}
+ // By the time we get here, the process should have already
+ // been stopped, so the app wouldn't get the stop reason,
+ // so just put USER instead of UNINSTALL or DISABLED.
cancelJobsForPackageAndUid(pkgName, pkgUid,
- "app disabled");
+ JobParameters.STOP_REASON_USER, "app disabled");
}
} catch (RemoteException|IllegalArgumentException e) {
/*
@@ -785,7 +788,11 @@
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
- cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
+ // By the time we get here, the process should have already
+ // been stopped, so the app wouldn't get the stop reason,
+ // so just put USER instead of UNINSTALL or DISABLED.
+ cancelJobsForPackageAndUid(pkgName, uidRemoved,
+ JobParameters.STOP_REASON_USER, "app uninstalled");
synchronized (mLock) {
for (int c = 0; c < mControllers.size(); ++c) {
mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
@@ -837,7 +844,8 @@
if (DEBUG) {
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
- cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
+ cancelJobsForPackageAndUid(pkgName, pkgUid,
+ JobParameters.STOP_REASON_USER, "app force stopped");
}
}
}
@@ -924,8 +932,7 @@
final String servicePkg = job.getService().getPackageName();
if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
// Only limit schedule calls for persisted jobs scheduled by the app itself.
- final String pkg =
- packageName == null ? job.getService().getPackageName() : packageName;
+ final String pkg = packageName == null ? servicePkg : packageName;
if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) {
// Don't log too frequently
@@ -972,14 +979,10 @@
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(uId,
- job.getService().getPackageName())) {
- Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
- + " -- package not allowed to start");
- return JobScheduler.RESULT_FAILURE;
- }
- } catch (RemoteException e) {
+ if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
+ Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ + " -- package not allowed to start");
+ return JobScheduler.RESULT_FAILURE;
}
synchronized (mLock) {
@@ -1029,7 +1032,8 @@
if (toCancel != null) {
// Implicitly replaces the existing job record with the new instance
- cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
+ cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
+ "job rescheduled by app");
} else {
startTrackingJobLocked(jobStatus, null);
}
@@ -1105,7 +1109,10 @@
final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
for (int i=0; i<jobsForUser.size(); i++) {
JobStatus toRemove = jobsForUser.get(i);
- cancelJobImplLocked(toRemove, null, "user removed");
+ // By the time we get here, the process should have already been stopped, so the
+ // app wouldn't get the stop reason, so just put USER instead of UNINSTALL.
+ cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
+ "user removed");
}
}
}
@@ -1117,7 +1124,8 @@
}
}
- void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
+ void cancelJobsForPackageAndUid(String pkgName, int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
if ("android".equals(pkgName)) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
return;
@@ -1127,7 +1135,7 @@
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
if (job.getSourcePackageName().equals(pkgName)) {
- cancelJobImplLocked(job, null, reason);
+ cancelJobImplLocked(job, null, reason, debugReason);
}
}
}
@@ -1137,10 +1145,11 @@
* Entry point from client to cancel all jobs originating from their uid.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
- * @param uid Uid to check against for removal of a job.
*
+ * @param uid Uid to check against for removal of a job.
*/
- public boolean cancelJobsForUid(int uid, String reason) {
+ public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return false;
@@ -1151,7 +1160,7 @@
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
- cancelJobImplLocked(toRemove, null, reason);
+ cancelJobImplLocked(toRemove, null, reason, debugReason);
jobsCanceled = true;
}
}
@@ -1162,15 +1171,17 @@
* Entry point from client to cancel the job corresponding to the jobId provided.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
- * @param uid Uid of the calling client.
+ *
+ * @param uid Uid of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
- public boolean cancelJob(int uid, int jobId, int callingUid) {
+ private boolean cancelJob(int uid, int jobId, int callingUid,
+ @JobParameters.StopReason int reason) {
JobStatus toCancel;
synchronized (mLock) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
- cancelJobImplLocked(toCancel, null,
+ cancelJobImplLocked(toCancel, null, reason,
"cancel() called by app, callingUid=" + callingUid
+ " uid=" + uid + " jobId=" + jobId);
}
@@ -1184,7 +1195,8 @@
* {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
* currently scheduled jobs.
*/
- private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
+ private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
+ @JobParameters.StopReason int reason, String debugReason) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked();
stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
@@ -1193,7 +1205,8 @@
mJobPackageTracker.noteNonpending(cancelled);
}
// Cancel if running.
- stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
+ stopJobOnServiceContextLocked(cancelled, reason, JobParameters.REASON_CANCELED,
+ debugReason);
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
@@ -1232,7 +1245,8 @@
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null && !executing.canRunInDoze()) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
+ jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_DEVICE_IDLE,
"cancelled due to doze");
}
}
@@ -1430,7 +1444,8 @@
if (DEBUG) {
Slog.v(TAG, " replacing " + oldJob + " with " + newJob);
}
- cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation");
+ cancelJobImplLocked(oldJob, newJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+ "deferred rtc calculation");
}
}
};
@@ -1550,12 +1565,13 @@
return removed;
}
- private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
+ private boolean stopJobOnServiceContextLocked(JobStatus job,
+ @JobParameters.StopReason int reason, int legacyReason, String debugReason) {
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
- jsc.cancelExecutingJobLocked(reason, debugReason);
+ jsc.cancelExecutingJobLocked(reason, legacyReason, debugReason);
return true;
}
}
@@ -1880,7 +1896,7 @@
queueReadyJobsForExecutionLocked();
break;
case MSG_STOP_JOB:
- cancelJobImplLocked((JobStatus) message.obj, null,
+ cancelJobImplLocked((JobStatus) message.obj, null, message.arg1,
"app no longer allowed to run");
break;
@@ -1895,7 +1911,9 @@
final boolean disabled = message.arg2 != 0;
updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (disabled) {
- cancelJobsForUid(uid, "uid gone");
+ cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ "uid gone");
}
synchronized (mLock) {
mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1913,7 +1931,9 @@
final int uid = message.arg1;
final boolean disabled = message.arg2 != 0;
if (disabled) {
- cancelJobsForUid(uid, "app uid idle");
+ cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ "app uid idle");
}
synchronized (mLock) {
mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1965,10 +1985,12 @@
if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX
&& !running.areDynamicConstraintsSatisfied()) {
serviceContext.cancelExecutingJobLocked(
+ running.getStopReason(),
JobParameters.REASON_RESTRICTED_BUCKET,
"cancelled due to restricted bucket");
} else {
serviceContext.cancelExecutingJobLocked(
+ running.getStopReason(),
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
"cancelled due to unsatisfied constraints");
}
@@ -1977,7 +1999,9 @@
if (restriction != null) {
final int reason = restriction.getReason();
serviceContext.cancelExecutingJobLocked(reason,
- "restricted due to " + JobParameters.getReasonCodeDescription(reason));
+ restriction.getLegacyReason(),
+ "restricted due to " + JobParameters.getLegacyReasonCodeDescription(
+ reason));
}
}
}
@@ -2058,15 +2082,14 @@
@Override
public void accept(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
- job.getJob().getService().getPackageName())) {
- Slog.w(TAG, "Aborting job " + job.getUid() + ":"
- + job.getJob().toString() + " -- package not allowed to start");
- mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
- return;
- }
- } catch (RemoteException e) {
+ if (mActivityManagerInternal.isAppStartModeDisabled(job.getUid(),
+ job.getJob().getService().getPackageName())) {
+ Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ + job.getJob().toString() + " -- package not allowed to start");
+ mHandler.obtainMessage(MSG_STOP_JOB,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job)
+ .sendToTarget();
+ return;
}
final boolean shouldForceBatchJob;
@@ -2276,7 +2299,7 @@
if (restriction != null) {
if (DEBUG) {
Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
- + " restricted due to " + restriction.getReason());
+ + " restricted due to " + restriction.getLegacyReason());
}
return false;
}
@@ -2367,8 +2390,9 @@
}
@Override
- public void cancelJobsForUid(int uid, String reason) {
- JobSchedulerService.this.cancelJobsForUid(uid, reason);
+ public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
+ JobSchedulerService.this.cancelJobsForUid(uid, reason, debugReason);
}
@Override
@@ -2706,6 +2730,7 @@
final long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP,
"cancelAll() called by app, callingUid=" + uid);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2718,7 +2743,8 @@
final long ident = Binder.clearCallingIdentity();
try {
- JobSchedulerService.this.cancelJob(uid, jobId, uid);
+ JobSchedulerService.this.cancelJob(uid, jobId, uid,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2924,12 +2950,13 @@
if (!hasJobId) {
pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
- if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+ if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER,
+ "cancel shell command for package")) {
pw.println("No matching jobs found.");
}
} else {
pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
- if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
+ if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
pw.println("No matching job found.");
}
}
@@ -3164,8 +3191,9 @@
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
final JobRestriction restriction = mJobRestrictions.get(i);
if (restriction.isJobRestricted(job)) {
- final int reason = restriction.getReason();
- pw.print(" " + JobParameters.getReasonCodeDescription(reason));
+ final int reason = restriction.getLegacyReason();
+ pw.print(" ");
+ pw.print(JobParameters.getLegacyReasonCodeDescription(reason));
}
}
} else {
@@ -3430,7 +3458,7 @@
final long restrictionsToken = proto.start(
JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS);
proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON,
- restriction.getReason());
+ restriction.getLegacyReason());
proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING,
restriction.isJobRestricted(job));
proto.end(restrictionsToken);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 9ef46df..e8bcbfb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -354,8 +354,9 @@
/** Called externally when a job that was scheduled for execution should be cancelled. */
@GuardedBy("mLock")
- void cancelExecutingJobLocked(int reason, @NonNull String debugReason) {
- doCancelLocked(reason, debugReason);
+ void cancelExecutingJobLocked(@JobParameters.StopReason int reason,
+ int legacyStopReason, @NonNull String debugReason) {
+ doCancelLocked(reason, legacyStopReason, debugReason);
}
int getPreferredUid() {
@@ -387,7 +388,8 @@
&& (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
&& (!matchJobId || jobId == executing.getJobId())) {
if (mVerb == VERB_EXECUTING) {
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+ mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.REASON_TIMEOUT, reason);
sendStopMessageLocked("force timeout from shell");
return true;
}
@@ -614,7 +616,8 @@
}
@GuardedBy("mLock")
- private void doCancelLocked(int stopReasonCode, @Nullable String debugReason) {
+ private void doCancelLocked(@JobParameters.StopReason int stopReasonCode, int legacyStopReason,
+ @Nullable String debugReason) {
if (mVerb == VERB_FINISHED) {
if (DEBUG) {
Slog.d(TAG,
@@ -622,8 +625,8 @@
}
return;
}
- mParams.setStopReason(stopReasonCode, debugReason);
- if (stopReasonCode == JobParameters.REASON_PREEMPT) {
+ mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason);
+ if (legacyStopReason == JobParameters.REASON_PREEMPT) {
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
NO_PREFERRED_UID;
}
@@ -781,7 +784,8 @@
// Not an error - client ran out of time.
Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
+ " Sending onStop: " + getRunningJobNameLocked());
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
+ mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.REASON_TIMEOUT, "client timed out");
sendStopMessageLocked("timeout while executing");
} else {
// We've given the app the minimum execution time. See if we should stop it or
@@ -790,7 +794,11 @@
if (reason != null) {
Slog.i(TAG, "Stopping client after min execution time: "
+ getRunningJobNameLocked() + " because " + reason);
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+ // Tell the developer we're stopping the job due to device state instead
+ // of timeout since all of the reasons could equate to "the system needs
+ // the resources the app is currently using."
+ mParams.setStopReason(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_TIMEOUT, reason);
sendStopMessageLocked(reason);
} else {
Slog.i(TAG, "Letting " + getRunningJobNameLocked()
@@ -844,12 +852,12 @@
}
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
- final int stopReason = mParams.getStopReason();
- mJobPackageTracker.noteInactive(completedJob, stopReason, reason);
+ final int legacyStopReason = mParams.getLegacyStopReason();
+ mJobPackageTracker.noteInactive(completedJob, legacyStopReason, reason);
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.getSourceUid(), null, completedJob.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
- stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+ legacyStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
completedJob.hasChargingConstraint(),
completedJob.hasBatteryNotLowConstraint(),
completedJob.hasStorageNotLowConstraint(),
@@ -860,7 +868,7 @@
completedJob.hasContentTriggerConstraint());
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
- stopReason);
+ legacyStopReason);
} catch (RemoteException e) {
// Whatever.
}
@@ -879,7 +887,7 @@
service = null;
mAvailable = true;
removeOpTimeOutLocked();
- mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule);
+ mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index aa8d98c..9cd3a8f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -541,18 +541,23 @@
/**
* Write out a tag with data identifying this job's constraints. If the constraint isn't here
* it doesn't apply.
+ * TODO: b/183455312 Update this code to use proper serialization for NetworkRequest,
+ * because currently store is not including everything (like, UIDs, bandwidth,
+ * signal strength etc. are lost).
*/
private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
if (jobStatus.hasConnectivityConstraint()) {
final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+ // STOPSHIP b/183071974: improve the scheme for backward compatibility and
+ // mainline cleanliness.
out.attribute(null, "net-capabilities", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+ BitUtils.packBits(network.getCapabilities())));
out.attribute(null, "net-unwanted-capabilities", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities())));
+ BitUtils.packBits(network.getUnwantedCapabilities())));
out.attribute(null, "net-transport-types", Long.toString(
- BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
+ BitUtils.packBits(network.getTransportTypes())));
}
if (jobStatus.hasIdleConstraint()) {
out.attribute(null, "idle", Boolean.toString(true));
@@ -976,18 +981,23 @@
null, "net-unwanted-capabilities");
final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
if (netCapabilities != null && netTransportTypes != null) {
- final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder()
+ .clearCapabilities();
final long unwantedCapabilities = netUnwantedCapabilities != null
? Long.parseLong(netUnwantedCapabilities)
- : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities());
-
+ : BitUtils.packBits(builder.build().getUnwantedCapabilities());
// We're okay throwing NFE here; caught by caller
- request.networkCapabilities.setCapabilities(
- BitUtils.unpackBits(Long.parseLong(netCapabilities)),
- BitUtils.unpackBits(unwantedCapabilities));
- request.networkCapabilities.setTransportTypes(
- BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
- jobBuilder.setRequiredNetwork(request);
+ for (int capability : BitUtils.unpackBits(Long.parseLong(netCapabilities))) {
+ builder.addCapability(capability);
+ }
+ for (int unwantedCapability : BitUtils.unpackBits(
+ Long.parseLong(netUnwantedCapabilities))) {
+ builder.addUnwantedCapability(unwantedCapability);
+ }
+ for (int transport : BitUtils.unpackBits(Long.parseLong(netTransportTypes))) {
+ builder.addTransportType(transport);
+ }
+ jobBuilder.setRequiredNetwork(builder.build());
} else {
// Read legacy values
val = parser.getAttributeValue(null, "connectivity");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index a230b23..548a1ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -210,7 +210,8 @@
jobStatus.maybeLogBucketMismatch();
}
boolean didChange =
- jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun);
+ jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun,
+ !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
didChange |= jobStatus.setUidActive(isActive);
return didChange;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 6e542f3..6825cf2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -22,6 +22,7 @@
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.net.ConnectivityManager;
@@ -380,15 +381,23 @@
}
}
+ private static NetworkCapabilities.Builder copyCapabilities(
+ @NonNull final NetworkRequest request) {
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+ for (int transport : request.getTransportTypes()) builder.addTransportType(transport);
+ for (int capability : request.getCapabilities()) builder.addCapability(capability);
+ return builder;
+ }
+
private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
NetworkCapabilities capabilities, Constants constants) {
// A restricted job that's out of quota MUST use an unmetered network.
if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
&& !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
- final NetworkCapabilities required = new NetworkCapabilities.Builder(
- jobStatus.getJob().getRequiredNetwork().networkCapabilities)
- .addCapability(NET_CAPABILITY_NOT_METERED).build();
- return required.satisfiedByNetworkCapabilities(capabilities);
+ final NetworkCapabilities.Builder builder =
+ copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+ builder.addCapability(NET_CAPABILITY_NOT_METERED);
+ return builder.build().satisfiedByNetworkCapabilities(capabilities);
} else {
return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities);
}
@@ -402,10 +411,10 @@
}
// See if we match after relaxing any unmetered request
- final NetworkCapabilities relaxed = new NetworkCapabilities.Builder(
- jobStatus.getJob().getRequiredNetwork().networkCapabilities)
- .removeCapability(NET_CAPABILITY_NOT_METERED).build();
- if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
+ final NetworkCapabilities.Builder builder =
+ copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+ builder.removeCapability(NET_CAPABILITY_NOT_METERED);
+ if (builder.build().satisfiedByNetworkCapabilities(capabilities)) {
// TODO: treat this as "maybe" response; need to check quotas
return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
} else {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index bad8dc1..8fa0d3e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -24,11 +24,13 @@
import android.app.AppGlobals;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
import android.net.Network;
+import android.net.NetworkRequest;
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -37,6 +39,7 @@
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
+import android.util.Range;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -54,6 +57,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.function.Predicate;
/**
@@ -353,6 +357,9 @@
*/
private long mLastFailedRunTime;
+ /** Whether or not the app is background restricted by the user (FAS). */
+ private boolean mIsUserBgRestricted;
+
/**
* Transient: when a job is inflated from disk before we have a reliable RTC clock time,
* we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
@@ -409,6 +416,9 @@
/** The job's dynamic requirements have been satisfied. */
private boolean mReadyDynamicSatisfied;
+ /** The reason a job most recently went from ready to not ready. */
+ private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return callingUid;
@@ -520,8 +530,15 @@
// Later, when we check if a given network satisfies the required
// network, we need to know the UID that is requesting it, so push
// our source UID into place.
- job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
+ final JobInfo.Builder builder = new JobInfo.Builder(job);
+ final NetworkRequest.Builder requestBuilder =
+ new NetworkRequest.Builder(job.getRequiredNetwork());
+ requestBuilder.setUids(
+ Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
+ builder.setRequiredNetwork(requestBuilder.build());
+ job = builder.build();
}
+
final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
mHasMediaBackupExemption = !job.hasLateConstraint() && exemptedMediaUrisOnly
&& requiresNetwork && this.sourcePackageName.equals(jsi.getMediaBackupPackage());
@@ -1042,6 +1059,11 @@
mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
}
+ @JobParameters.StopReason
+ public int getStopReason() {
+ return mReasonReadyToUnready;
+ }
+
/**
* Return the fractional position of "now" within the "run time" window of
* this job.
@@ -1172,7 +1194,9 @@
}
/** @return true if the constraint was changed, false otherwise. */
- boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state) {
+ boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state,
+ boolean isUserBgRestricted) {
+ mIsUserBgRestricted = isUserBgRestricted;
if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, nowElapsed, state)) {
// The constraint was changed. Update the ready flag.
mReadyNotRestrictedInBg = state;
@@ -1226,6 +1250,7 @@
"Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for "
+ toShortString());
}
+ final boolean wasReady = !state && isReady();
satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
mReadyDynamicSatisfied = mDynamicConstraints != 0
@@ -1244,9 +1269,81 @@
mConstraintChangeHistoryIndex =
(mConstraintChangeHistoryIndex + 1) % NUM_CONSTRAINT_CHANGE_HISTORY;
+ // Can't use isReady() directly since "cache booleans" haven't updated yet.
+ final boolean isReady = readinessStatusWithConstraint(constraint, state);
+ if (wasReady && !isReady) {
+ mReasonReadyToUnready = constraintToStopReason(constraint);
+ } else if (!wasReady && isReady) {
+ mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+ }
+
return true;
}
+ @JobParameters.StopReason
+ private int constraintToStopReason(int constraint) {
+ switch (constraint) {
+ case CONSTRAINT_BATTERY_NOT_LOW:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_CHARGING:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_CHARGING;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_CONNECTIVITY:
+ return JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY;
+ case CONSTRAINT_IDLE:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_DEVICE_IDLE;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_STORAGE_NOT_LOW:
+ return JobParameters.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+
+ case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
+ // The BACKGROUND_NOT_RESTRICTED constraint could be dissatisfied either because
+ // the app is background restricted, or because we're restricting background work
+ // in battery saver. Assume that background restriction is the reason apps that
+ // are background restricted have their jobs stopped, and battery saver otherwise.
+ // This has the benefit of being consistent for background restricted apps
+ // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+ // battery saver state.
+ if (mIsUserBgRestricted) {
+ return JobParameters.STOP_REASON_BACKGROUND_RESTRICTION;
+ }
+ return JobParameters.STOP_REASON_DEVICE_STATE;
+ case CONSTRAINT_DEVICE_NOT_DOZING:
+ return JobParameters.STOP_REASON_DEVICE_STATE;
+
+ case CONSTRAINT_WITHIN_QUOTA:
+ case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
+ return JobParameters.STOP_REASON_QUOTA;
+
+ // These should never be stop reasons since they can never go from true to false.
+ case CONSTRAINT_CONTENT_TRIGGER:
+ case CONSTRAINT_DEADLINE:
+ case CONSTRAINT_TIMING_DELAY:
+ default:
+ Slog.wtf(TAG, "Unsupported constraint (" + constraint + ") --stop reason mapping");
+ return JobParameters.STOP_REASON_UNDEFINED;
+ }
+ }
+
boolean isConstraintSatisfied(int constraint) {
return (satisfiedConstraints&constraint) != 0;
}
@@ -1330,33 +1427,42 @@
* granted, based on its requirements.
*/
boolean wouldBeReadyWithConstraint(int constraint) {
+ return readinessStatusWithConstraint(constraint, true);
+ }
+
+ private boolean readinessStatusWithConstraint(int constraint, boolean value) {
boolean oldValue = false;
int satisfied = mSatisfiedConstraintsOfInterest;
switch (constraint) {
case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
oldValue = mReadyNotRestrictedInBg;
- mReadyNotRestrictedInBg = true;
+ mReadyNotRestrictedInBg = value;
break;
case CONSTRAINT_DEADLINE:
oldValue = mReadyDeadlineSatisfied;
- mReadyDeadlineSatisfied = true;
+ mReadyDeadlineSatisfied = value;
break;
case CONSTRAINT_DEVICE_NOT_DOZING:
oldValue = mReadyNotDozing;
- mReadyNotDozing = true;
+ mReadyNotDozing = value;
break;
case CONSTRAINT_WITHIN_QUOTA:
oldValue = mReadyWithinQuota;
- mReadyWithinQuota = true;
+ mReadyWithinQuota = value;
break;
case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
oldValue = mReadyWithinExpeditedQuota;
- mReadyWithinExpeditedQuota = true;
+ mReadyWithinExpeditedQuota = value;
break;
default:
- satisfied |= constraint;
+ if (value) {
+ satisfied |= constraint;
+ } else {
+ satisfied &= ~constraint;
+ }
mReadyDynamicSatisfied = mDynamicConstraints != 0
&& mDynamicConstraints == (satisfied & mDynamicConstraints);
+
break;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index ac59f95..2962b10 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -17,6 +17,7 @@
package com.android.server.job.restrictions;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
@@ -26,9 +27,8 @@
/**
* Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
* should be scheduled or not based on the state of the system/device.
- * Every restriction is associated with exactly one reason (from {@link
- * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link
- * #getReason()}.
+ * Every restriction is associated with exactly one stop reason, which could be retrieved using
+ * {@link #getReason()} (and the legacy reason via {@link #getLegacyReason()}).
* Note, that this is not taken into account for the jobs that have priority
* {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
*/
@@ -36,10 +36,13 @@
final JobSchedulerService mService;
private final int mReason;
+ private final int mLegacyReason;
- JobRestriction(JobSchedulerService service, int reason) {
+ JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
+ int legacyReason) {
mService = service;
mReason = reason;
+ mLegacyReason = legacyReason;
}
/**
@@ -66,7 +69,12 @@
public abstract void dumpConstants(ProtoOutputStream proto);
/** @return reason code for the Restriction. */
+ @JobParameters.StopReason
public final int getReason() {
return mReason;
}
+
+ public final int getLegacyReason() {
+ return mLegacyReason;
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index 954a5b8..8b699e9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -34,7 +34,7 @@
private PowerManager mPowerManager;
public ThermalStatusRestriction(JobSchedulerService service) {
- super(service, JobParameters.REASON_DEVICE_THERMAL);
+ super(service, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.REASON_DEVICE_THERMAL);
}
@Override
diff --git a/core/api/current.txt b/core/api/current.txt
index 7e2edd6..0f00b54 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1729,42 +1729,66 @@
field @Deprecated public static final int secondary_text_dark_nodisable = 17170438; // 0x1060006
field @Deprecated public static final int secondary_text_light = 17170439; // 0x1060007
field @Deprecated public static final int secondary_text_light_nodisable = 17170440; // 0x1060008
- field public static final int system_neutral_0 = 17170485; // 0x1060035
- field public static final int system_neutral_100 = 17170487; // 0x1060037
- field public static final int system_neutral_1000 = 17170496; // 0x1060040
- field public static final int system_neutral_200 = 17170488; // 0x1060038
- field public static final int system_neutral_300 = 17170489; // 0x1060039
- field public static final int system_neutral_400 = 17170490; // 0x106003a
- field public static final int system_neutral_50 = 17170486; // 0x1060036
- field public static final int system_neutral_500 = 17170491; // 0x106003b
- field public static final int system_neutral_600 = 17170492; // 0x106003c
- field public static final int system_neutral_700 = 17170493; // 0x106003d
- field public static final int system_neutral_800 = 17170494; // 0x106003e
- field public static final int system_neutral_900 = 17170495; // 0x106003f
- field public static final int system_primary_0 = 17170461; // 0x106001d
- field public static final int system_primary_100 = 17170463; // 0x106001f
- field public static final int system_primary_1000 = 17170472; // 0x1060028
- field public static final int system_primary_200 = 17170464; // 0x1060020
- field public static final int system_primary_300 = 17170465; // 0x1060021
- field public static final int system_primary_400 = 17170466; // 0x1060022
- field public static final int system_primary_50 = 17170462; // 0x106001e
- field public static final int system_primary_500 = 17170467; // 0x1060023
- field public static final int system_primary_600 = 17170468; // 0x1060024
- field public static final int system_primary_700 = 17170469; // 0x1060025
- field public static final int system_primary_800 = 17170470; // 0x1060026
- field public static final int system_primary_900 = 17170471; // 0x1060027
- field public static final int system_secondary_0 = 17170473; // 0x1060029
- field public static final int system_secondary_100 = 17170475; // 0x106002b
- field public static final int system_secondary_1000 = 17170484; // 0x1060034
- field public static final int system_secondary_200 = 17170476; // 0x106002c
- field public static final int system_secondary_300 = 17170477; // 0x106002d
- field public static final int system_secondary_400 = 17170478; // 0x106002e
- field public static final int system_secondary_50 = 17170474; // 0x106002a
- field public static final int system_secondary_500 = 17170479; // 0x106002f
- field public static final int system_secondary_600 = 17170480; // 0x1060030
- field public static final int system_secondary_700 = 17170481; // 0x1060031
- field public static final int system_secondary_800 = 17170482; // 0x1060032
- field public static final int system_secondary_900 = 17170483; // 0x1060033
+ field public static final int system_accent1_0 = 17170485; // 0x1060035
+ field public static final int system_accent1_100 = 17170487; // 0x1060037
+ field public static final int system_accent1_1000 = 17170496; // 0x1060040
+ field public static final int system_accent1_200 = 17170488; // 0x1060038
+ field public static final int system_accent1_300 = 17170489; // 0x1060039
+ field public static final int system_accent1_400 = 17170490; // 0x106003a
+ field public static final int system_accent1_50 = 17170486; // 0x1060036
+ field public static final int system_accent1_500 = 17170491; // 0x106003b
+ field public static final int system_accent1_600 = 17170492; // 0x106003c
+ field public static final int system_accent1_700 = 17170493; // 0x106003d
+ field public static final int system_accent1_800 = 17170494; // 0x106003e
+ field public static final int system_accent1_900 = 17170495; // 0x106003f
+ field public static final int system_accent2_0 = 17170497; // 0x1060041
+ field public static final int system_accent2_100 = 17170499; // 0x1060043
+ field public static final int system_accent2_1000 = 17170508; // 0x106004c
+ field public static final int system_accent2_200 = 17170500; // 0x1060044
+ field public static final int system_accent2_300 = 17170501; // 0x1060045
+ field public static final int system_accent2_400 = 17170502; // 0x1060046
+ field public static final int system_accent2_50 = 17170498; // 0x1060042
+ field public static final int system_accent2_500 = 17170503; // 0x1060047
+ field public static final int system_accent2_600 = 17170504; // 0x1060048
+ field public static final int system_accent2_700 = 17170505; // 0x1060049
+ field public static final int system_accent2_800 = 17170506; // 0x106004a
+ field public static final int system_accent2_900 = 17170507; // 0x106004b
+ field public static final int system_accent3_0 = 17170509; // 0x106004d
+ field public static final int system_accent3_100 = 17170511; // 0x106004f
+ field public static final int system_accent3_1000 = 17170520; // 0x1060058
+ field public static final int system_accent3_200 = 17170512; // 0x1060050
+ field public static final int system_accent3_300 = 17170513; // 0x1060051
+ field public static final int system_accent3_400 = 17170514; // 0x1060052
+ field public static final int system_accent3_50 = 17170510; // 0x106004e
+ field public static final int system_accent3_500 = 17170515; // 0x1060053
+ field public static final int system_accent3_600 = 17170516; // 0x1060054
+ field public static final int system_accent3_700 = 17170517; // 0x1060055
+ field public static final int system_accent3_800 = 17170518; // 0x1060056
+ field public static final int system_accent3_900 = 17170519; // 0x1060057
+ field public static final int system_neutral1_0 = 17170461; // 0x106001d
+ field public static final int system_neutral1_100 = 17170463; // 0x106001f
+ field public static final int system_neutral1_1000 = 17170472; // 0x1060028
+ field public static final int system_neutral1_200 = 17170464; // 0x1060020
+ field public static final int system_neutral1_300 = 17170465; // 0x1060021
+ field public static final int system_neutral1_400 = 17170466; // 0x1060022
+ field public static final int system_neutral1_50 = 17170462; // 0x106001e
+ field public static final int system_neutral1_500 = 17170467; // 0x1060023
+ field public static final int system_neutral1_600 = 17170468; // 0x1060024
+ field public static final int system_neutral1_700 = 17170469; // 0x1060025
+ field public static final int system_neutral1_800 = 17170470; // 0x1060026
+ field public static final int system_neutral1_900 = 17170471; // 0x1060027
+ field public static final int system_neutral2_0 = 17170473; // 0x1060029
+ field public static final int system_neutral2_100 = 17170475; // 0x106002b
+ field public static final int system_neutral2_1000 = 17170484; // 0x1060034
+ field public static final int system_neutral2_200 = 17170476; // 0x106002c
+ field public static final int system_neutral2_300 = 17170477; // 0x106002d
+ field public static final int system_neutral2_400 = 17170478; // 0x106002e
+ field public static final int system_neutral2_50 = 17170474; // 0x106002a
+ field public static final int system_neutral2_500 = 17170479; // 0x106002f
+ field public static final int system_neutral2_600 = 17170480; // 0x1060030
+ field public static final int system_neutral2_700 = 17170481; // 0x1060031
+ field public static final int system_neutral2_800 = 17170482; // 0x1060032
+ field public static final int system_neutral2_900 = 17170483; // 0x1060033
field public static final int tab_indicator_text = 17170441; // 0x1060009
field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -5656,6 +5680,7 @@
field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
field public static final String EXTRA_PICTURE = "android.picture";
field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
+ field public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
field public static final String EXTRA_PROGRESS = "android.progress";
field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
@@ -5804,8 +5829,9 @@
method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.Bitmap);
method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.Bitmap);
- method @NonNull public android.app.Notification.BigPictureStyle bigPictureContentDescription(@Nullable CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.BigPictureStyle setBigContentTitle(@Nullable CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle setContentDescription(@Nullable CharSequence);
method @NonNull public android.app.Notification.BigPictureStyle setSummaryText(@Nullable CharSequence);
method @NonNull public android.app.Notification.BigPictureStyle showBigPictureWhenCollapsed(boolean);
}
@@ -7157,6 +7183,7 @@
method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName);
method public boolean isDeviceIdAttestationSupported();
method public boolean isDeviceOwnerApp(String);
+ method public boolean isEnterpriseNetworkPreferenceEnabled();
method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
method public boolean isKeyPairGrantedToWifiAuth(@NonNull String);
method public boolean isLockTaskPermitted(String);
@@ -7164,7 +7191,6 @@
method public boolean isManagedProfile(@NonNull android.content.ComponentName);
method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
- method public boolean isNetworkSlicingEnabled();
method public boolean isOrganizationOwnedDeviceWithManagedProfile();
method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -7219,6 +7245,7 @@
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+ method public void setEnterpriseNetworkPreferenceEnabled(boolean);
method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
@@ -7238,7 +7265,6 @@
method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
- method public void setNetworkSlicingEnabled(boolean);
method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int);
method public void setOrganizationId(@NonNull String);
method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -7944,6 +7970,7 @@
method @NonNull public android.os.PersistableBundle getExtras();
method public int getJobId();
method @Nullable public android.net.Network getNetwork();
+ method public int getStopReason();
method @NonNull public android.os.Bundle getTransientExtras();
method @Nullable public String[] getTriggeredContentAuthorities();
method @Nullable public android.net.Uri[] getTriggeredContentUris();
@@ -7952,6 +7979,21 @@
method public boolean isOverrideDeadlineExpired();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR;
+ field public static final int STOP_REASON_APP_STANDBY = 12; // 0xc
+ field public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; // 0xb
+ field public static final int STOP_REASON_CANCELLED_BY_APP = 1; // 0x1
+ field public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; // 0x5
+ field public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; // 0x6
+ field public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; // 0x7
+ field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+ field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
+ field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
+ field public static final int STOP_REASON_PREEMPT = 2; // 0x2
+ field public static final int STOP_REASON_QUOTA = 10; // 0xa
+ field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
+ field public static final int STOP_REASON_TIMEOUT = 3; // 0x3
+ field public static final int STOP_REASON_UNDEFINED = 0; // 0x0
+ field public static final int STOP_REASON_USER = 13; // 0xd
}
public abstract class JobScheduler {
@@ -8967,6 +9009,8 @@
field public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
field public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
field public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
+ field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
+ field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
field public static final int BOND_BONDED = 12; // 0xc
field public static final int BOND_BONDING = 11; // 0xb
field public static final int BOND_NONE = 10; // 0xa
@@ -21297,6 +21341,7 @@
method public void setCallback(@Nullable android.media.MediaCodec.Callback, @Nullable android.os.Handler);
method public void setCallback(@Nullable android.media.MediaCodec.Callback);
method public void setInputSurface(@NonNull android.view.Surface);
+ method public void setOnFirstTunnelFrameReadyListener(@Nullable android.os.Handler, @Nullable android.media.MediaCodec.OnFirstTunnelFrameReadyListener);
method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
method public void setOutputSurface(@NonNull android.view.Surface);
method public void setParameters(@Nullable android.os.Bundle);
@@ -21324,6 +21369,7 @@
field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us";
+ field public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek";
field public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
@@ -21414,6 +21460,10 @@
field public static final String WIDTH = "android.media.mediacodec.width";
}
+ public static interface MediaCodec.OnFirstTunnelFrameReadyListener {
+ method public void onFirstTunnelFrameReady(@NonNull android.media.MediaCodec);
+ }
+
public static interface MediaCodec.OnFrameRenderedListener {
method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long);
}
@@ -39159,7 +39209,7 @@
method public android.telecom.Call getParent();
method public String getRemainingPostDialSequence();
method @Nullable public android.telecom.Call.RttCall getRttCall();
- method public int getState();
+ method @Deprecated public int getState();
method public android.telecom.InCallService.VideoCall getVideoCall();
method public void handoverTo(android.telecom.PhoneAccountHandle, int, android.os.Bundle);
method public void hold();
@@ -39254,6 +39304,7 @@
method public android.net.Uri getHandle();
method public int getHandlePresentation();
method public android.os.Bundle getIntentExtras();
+ method public final int getState();
method public android.telecom.StatusHints getStatusHints();
method public int getVideoState();
method public static boolean hasProperty(int, int);
@@ -42099,7 +42150,7 @@
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
- method public int getDeviceToDeviceStatusSharing(int);
+ method public int getDeviceToDeviceStatusSharingPreference(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
method public static int getSlotIndex(int);
method @Nullable public int[] getSubscriptionIds(int);
@@ -42112,7 +42163,7 @@
method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharing(int, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
method public void setSubscriptionOverrideCongested(int, boolean, long);
method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long);
@@ -42335,6 +42386,7 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getNetworkSlicingConfiguration(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.data.SlicingConfig,android.telephony.TelephonyManager.SlicingException>);
method public String getNetworkSpecifier();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
+ method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
@@ -42371,6 +42423,7 @@
method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
method public boolean isConcurrentVoiceAndDataSupported();
+ method public boolean isDataCapable();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
@@ -52476,9 +52529,43 @@
package android.view.translation {
+ public final class TranslationCapability implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+ method public int getState();
+ method public int getSupportedTranslationFlags();
+ method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+ method public boolean isUiTranslationEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationCapability> CREATOR;
+ field public static final int STATE_AVAILABLE_TO_DOWNLOAD = 1; // 0x1
+ field public static final int STATE_DOWNLOADING = 2; // 0x2
+ field public static final int STATE_ON_DEVICE = 3; // 0x3
+ }
+
+ public final class TranslationContext implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+ method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+ method public int getTranslationFlags();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationContext> CREATOR;
+ field public static final int FLAG_DICTIONARY_DESCRIPTION = 4; // 0x4
+ field public static final int FLAG_LOW_LATENCY = 1; // 0x1
+ field public static final int FLAG_TRANSLITERATION = 2; // 0x2
+ }
+
+ public static final class TranslationContext.Builder {
+ ctor public TranslationContext.Builder(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
+ method @NonNull public android.view.translation.TranslationContext build();
+ method @NonNull public android.view.translation.TranslationContext.Builder setTranslationFlags(int);
+ }
+
public final class TranslationManager {
- method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
- method @NonNull @WorkerThread public java.util.List<java.lang.String> getSupportedLocales();
+ method public void addTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
+ method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationContext);
+ method @NonNull @WorkerThread public java.util.Set<android.view.translation.TranslationCapability> getTranslationCapabilities(int, int);
+ method public void removeTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
}
public final class TranslationRequest implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 13b83796..a0e0f71 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -65,6 +65,7 @@
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
field public static final String BRICK = "android.permission.BRICK";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
+ field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
@@ -698,6 +699,9 @@
method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int);
method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean);
+ field public static final int PASSWORD = 0; // 0x0
+ field public static final int PATTERN = 2; // 0x2
+ field public static final int PIN = 1; // 0x1
}
public class Notification implements android.os.Parcelable {
@@ -1191,12 +1195,14 @@
public class RestoreSet implements android.os.Parcelable {
ctor public RestoreSet();
- ctor public RestoreSet(String, String, long);
+ ctor public RestoreSet(@Nullable String, @Nullable String, long);
+ ctor public RestoreSet(@Nullable String, @Nullable String, long, int);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.backup.RestoreSet> CREATOR;
- field public String device;
- field public String name;
+ field public final int backupTransportFlags;
+ field @Nullable public String device;
+ field @Nullable public String name;
field public long token;
}
@@ -1832,6 +1838,7 @@
package android.apphibernation {
public final class AppHibernationManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean);
@@ -2138,7 +2145,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ResultStorageDescriptor> CREATOR;
}
+ public final class ScanFilter implements android.os.Parcelable {
+ method public int getAddressType();
+ method @Nullable public byte[] getIrk();
+ }
+
+ public static final class ScanFilter.Builder {
+ method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int);
+ method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int, @NonNull byte[]);
+ field public static final int LEN_IRK_OCTETS = 16; // 0x10
+ }
+
public final class ScanSettings implements android.os.Parcelable {
+ field public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3; // 0x3
field public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1; // 0x1
field public static final int SCAN_RESULT_TYPE_FULL = 0; // 0x0
}
@@ -5212,6 +5231,7 @@
method @Nullable public String getClientPackageName();
method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
+ method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback);
method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
method public void startScan();
method public void stopScan();
@@ -8254,9 +8274,10 @@
public class PowerExemptionManager {
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull String);
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull java.util.List<java.lang.String>);
- method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String, long, int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String, int, int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromAllowList(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String, int, @Nullable String, long);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String, int, @Nullable String, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public int[] getAllowListedAppIds(boolean);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromPermanentAllowList(@NonNull String);
field public static final int EVENT_MMS = 2; // 0x2
field public static final int EVENT_SMS = 1; // 0x1
field public static final int EVENT_UNSPECIFIED = 0; // 0x0
@@ -8480,6 +8501,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser();
@@ -8493,6 +8515,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean sharesMediaWithParent();
field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
@@ -8506,6 +8529,7 @@
field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2
field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
+ field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
}
@@ -10243,9 +10267,10 @@
ctor public TranslationService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onConnected();
- method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, int);
+ method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationContext, int);
method public void onDisconnected();
method public abstract void onFinishTranslationSession(int);
+ method public abstract void onTranslationCapabilitiesRequest(int, int, @NonNull java.util.function.Consumer<java.util.Set<android.view.translation.TranslationCapability>>);
method public abstract void onTranslationRequest(@NonNull android.view.translation.TranslationRequest, int, @NonNull android.os.CancellationSignal, @NonNull android.service.translation.TranslationService.OnTranslationResultCallback);
field public static final String SERVICE_INTERFACE = "android.service.translation.TranslationService";
field public static final String SERVICE_META_DATA = "android.translation_service";
@@ -11055,7 +11080,8 @@
public final class DataSpecificRegistrationInfo implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
+ method @Deprecated @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
+ method @Nullable public android.telephony.VopsSupportInfo getVopsSupportInfo();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DataSpecificRegistrationInfo> CREATOR;
}
@@ -11100,14 +11126,16 @@
field public static final int LCE_TYPE_SECONDARY = 1; // 0x1
}
- public final class LteVopsSupportInfo implements android.os.Parcelable {
+ public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public LteVopsSupportInfo(int, int);
- method public int describeContents();
method public int getEmcBearerSupport();
method public int getVopsSupport();
- method public void writeToParcel(android.os.Parcel, int);
+ method public boolean isEmergencyServiceFallbackSupported();
+ method public boolean isEmergencyServiceSupported();
+ method public boolean isVopsSupported();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LteVopsSupportInfo> CREATOR;
- field public static final int LTE_STATUS_NOT_AVAILABLE = 1; // 0x1
+ field @Deprecated public static final int LTE_STATUS_NOT_AVAILABLE = 1; // 0x1
field public static final int LTE_STATUS_NOT_SUPPORTED = 3; // 0x3
field public static final int LTE_STATUS_SUPPORTED = 2; // 0x2
}
@@ -11197,6 +11225,29 @@
field public static final int RESULT_SUCCESS = 0; // 0x0
}
+ public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
+ ctor public NrVopsSupportInfo(int, int, int);
+ method public int getEmcSupport();
+ method public int getEmfSupport();
+ method public int getVopsSupport();
+ method public boolean isEmergencyServiceFallbackSupported();
+ method public boolean isEmergencyServiceSupported();
+ method public boolean isVopsSupported();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NrVopsSupportInfo> CREATOR;
+ field public static final int NR_STATUS_EMC_5GCN_ONLY = 1; // 0x1
+ field public static final int NR_STATUS_EMC_EUTRA_5GCN_ONLY = 2; // 0x2
+ field public static final int NR_STATUS_EMC_NOT_SUPPORTED = 0; // 0x0
+ field public static final int NR_STATUS_EMC_NR_EUTRA_5GCN = 3; // 0x3
+ field public static final int NR_STATUS_EMF_5GCN_ONLY = 1; // 0x1
+ field public static final int NR_STATUS_EMF_EUTRA_5GCN_ONLY = 2; // 0x2
+ field public static final int NR_STATUS_EMF_NOT_SUPPORTED = 0; // 0x0
+ field public static final int NR_STATUS_EMF_NR_EUTRA_5GCN = 3; // 0x3
+ field public static final int NR_STATUS_VOPS_3GPP_SUPPORTED = 1; // 0x1
+ field public static final int NR_STATUS_VOPS_NON_3GPP_SUPPORTED = 2; // 0x2
+ field public static final int NR_STATUS_VOPS_NOT_SUPPORTED = 0; // 0x0
+ }
+
public interface NumberVerificationCallback {
method public default void onCallReceived(@NonNull String);
method public default void onVerificationFailed(int);
@@ -11862,6 +11913,7 @@
field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
+ field public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING = "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING";
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -12023,6 +12075,16 @@
method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
}
+ public abstract class VopsSupportInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public abstract boolean equals(Object);
+ method public abstract int hashCode();
+ method public abstract boolean isEmergencyServiceFallbackSupported();
+ method public abstract boolean isEmergencyServiceSupported();
+ method public abstract boolean isVopsSupported();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.VopsSupportInfo> CREATOR;
+ }
+
}
package android.telephony.cdma {
@@ -13240,6 +13302,7 @@
method @Nullable public android.telephony.ims.RcsContactPresenceTuple getCapabilityTuple(@NonNull String);
method @NonNull public java.util.List<android.telephony.ims.RcsContactPresenceTuple> getCapabilityTuples();
method @NonNull public android.net.Uri getContactUri();
+ method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
method public int getRequestResult();
method public int getSourceType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -13254,6 +13317,14 @@
field public static final int SOURCE_TYPE_NETWORK = 0; // 0x0
}
+ public static final class RcsContactUceCapability.OptionsBuilder {
+ ctor public RcsContactUceCapability.OptionsBuilder(@NonNull android.net.Uri);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTag(@NonNull String);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTags(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder setRequestResult(int);
+ }
+
public static final class RcsContactUceCapability.PresenceBuilder {
ctor public RcsContactUceCapability.PresenceBuilder(@NonNull android.net.Uri, int, int);
method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuple(@NonNull android.telephony.ims.RcsContactPresenceTuple);
@@ -13547,7 +13618,7 @@
package android.telephony.ims.stub {
public interface CapabilityExchangeEventListener {
- method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
+ method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
method public void onUnpublish() throws android.telephony.ims.ImsException;
}
@@ -13744,7 +13815,7 @@
public class RcsCapabilityExchangeImplBase {
ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
- method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
+ method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
method public void subscribeForCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3
field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1
@@ -14204,6 +14275,10 @@
package android.view.translation {
+ public final class TranslationCapability implements android.os.Parcelable {
+ ctor public TranslationCapability(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, boolean, int);
+ }
+
public final class UiTranslationManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 97e107c..7bc1993 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -29,7 +29,6 @@
field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
field public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS";
field public static final String QUERY_AUDIO_STATE = "android.permission.QUERY_AUDIO_STATE";
- field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
@@ -107,7 +106,6 @@
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
- field public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L; // 0xa6929b8L
field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL
field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf
field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
@@ -819,6 +817,7 @@
method public int describeContents();
method public android.os.UserHandle getUserHandle();
method public boolean isAdmin();
+ method public boolean isCloneProfile();
method public boolean isDemo();
method public boolean isEnabled();
method public boolean isEphemeral();
@@ -1713,35 +1712,16 @@
field public static final int[] RINGTONES;
}
- public static class VibrationEffect.OneShot extends android.os.VibrationEffect implements android.os.Parcelable {
- ctor public VibrationEffect.OneShot(android.os.Parcel);
- ctor public VibrationEffect.OneShot(long, int);
- method public int getAmplitude();
- method public long getDuration();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.OneShot> CREATOR;
- }
-
- public static class VibrationEffect.Prebaked extends android.os.VibrationEffect implements android.os.Parcelable {
- ctor public VibrationEffect.Prebaked(android.os.Parcel);
- ctor public VibrationEffect.Prebaked(int, boolean, int);
- method public long getDuration();
- method public int getEffectStrength();
- method public int getId();
- method public boolean shouldFallback();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Prebaked> CREATOR;
- }
-
- public static class VibrationEffect.Waveform extends android.os.VibrationEffect implements android.os.Parcelable {
- ctor public VibrationEffect.Waveform(android.os.Parcel);
- ctor public VibrationEffect.Waveform(long[], int[], int);
- method public int[] getAmplitudes();
+ public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+ method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int);
method public long getDuration();
method public int getRepeatIndex();
- method public long[] getTimings();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Waveform> CREATOR;
+ method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
+ method @NonNull public android.os.VibrationEffect.Composed resolve(int);
+ method @NonNull public android.os.VibrationEffect.Composed scale(float);
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
}
public class VintfObject {
@@ -1858,6 +1838,63 @@
}
+package android.os.vibrator {
+
+ public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment {
+ method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int);
+ method public int describeContents();
+ method public long getDuration();
+ method public int getEffectId();
+ method public int getEffectStrength();
+ method public boolean hasNonZeroAmplitude();
+ method @NonNull public android.os.vibrator.PrebakedSegment resolve(int);
+ method @NonNull public android.os.vibrator.PrebakedSegment scale(float);
+ method public boolean shouldFallback();
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR;
+ }
+
+ public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment {
+ method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int);
+ method public int describeContents();
+ method public int getDelay();
+ method public long getDuration();
+ method public int getPrimitiveId();
+ method public float getScale();
+ method public boolean hasNonZeroAmplitude();
+ method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int);
+ method @NonNull public android.os.vibrator.PrimitiveSegment scale(float);
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR;
+ }
+
+ public final class StepSegment extends android.os.vibrator.VibrationEffectSegment {
+ method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int);
+ method public int describeContents();
+ method public float getAmplitude();
+ method public long getDuration();
+ method public boolean hasNonZeroAmplitude();
+ method @NonNull public android.os.vibrator.StepSegment resolve(int);
+ method @NonNull public android.os.vibrator.StepSegment scale(float);
+ method public void validate();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR;
+ }
+
+ public abstract class VibrationEffectSegment implements android.os.Parcelable {
+ method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int);
+ method public abstract long getDuration();
+ method public abstract boolean hasNonZeroAmplitude();
+ method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int);
+ method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float);
+ method public abstract void validate();
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR;
+ }
+
+}
+
package android.permission {
public final class PermissionControllerManager {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index a536efb..1833ed5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1515,30 +1515,6 @@
MissingNullability: android.os.VibrationEffect#get(int, boolean):
-MissingNullability: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.OneShot#scale(float, int):
-
-MissingNullability: android.os.VibrationEffect.OneShot#writeToParcel(android.os.Parcel, int) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Prebaked#writeToParcel(android.os.Parcel, int) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #0:
-
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #1:
-
-MissingNullability: android.os.VibrationEffect.Waveform#getAmplitudes():
-
-MissingNullability: android.os.VibrationEffect.Waveform#getTimings():
-
-MissingNullability: android.os.VibrationEffect.Waveform#scale(float, int):
-
-MissingNullability: android.os.VibrationEffect.Waveform#writeToParcel(android.os.Parcel, int) parameter #0:
-
MissingNullability: android.os.VintfObject#getHalNamesAndVersions():
MissingNullability: android.os.VintfObject#getSepolicyVersion():
@@ -2739,12 +2715,6 @@
ParcelConstructor: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel):
-ParcelConstructor: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel):
-
-ParcelConstructor: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel):
-
-ParcelConstructor: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel):
-
ParcelConstructor: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.Parcel):
ParcelConstructor: android.service.notification.SnoozeCriterion#SnoozeCriterion(android.os.Parcel):
@@ -2773,12 +2743,6 @@
ParcelCreator: android.net.metrics.ValidationProbeEvent:
-ParcelCreator: android.os.VibrationEffect.OneShot:
-
-ParcelCreator: android.os.VibrationEffect.Prebaked:
-
-ParcelCreator: android.os.VibrationEffect.Waveform:
-
ParcelCreator: android.service.autofill.InternalOnClickAction:
ParcelCreator: android.service.autofill.InternalSanitizer:
@@ -2797,12 +2761,6 @@
ParcelNotFinal: android.os.IncidentManager.IncidentReport:
-ParcelNotFinal: android.os.VibrationEffect.OneShot:
-
-ParcelNotFinal: android.os.VibrationEffect.Prebaked:
-
-ParcelNotFinal: android.os.VibrationEffect.Waveform:
-
ParcelNotFinal: android.os.health.HealthStatsParceler:
ParcelNotFinal: android.service.autofill.InternalOnClickAction:
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3fedda3..36c66e5 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -901,7 +901,6 @@
*
* @hide
*/
- @TestApi
@ChangeId
public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a1dce77..006b2b5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1606,7 +1606,8 @@
/**
* Sets a launch cookie that can be used to track the activity and task that are launch as a
- * result of this option.
+ * result of this option. If the launched activity is a trampoline that starts another activity
+ * immediately, the cookie will be transferred to the next activity.
*
* @hide
*/
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 146d648..618eda8 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -22,6 +22,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
+import android.content.ComponentCallbacksController;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -53,14 +54,14 @@
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private static final String TAG = "Application";
@UnsupportedAppUsage
- private ArrayList<ComponentCallbacks> mComponentCallbacks =
- new ArrayList<ComponentCallbacks>();
- @UnsupportedAppUsage
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
@UnsupportedAppUsage
private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
+
/** @hide */
@UnsupportedAppUsage
public LoadedApk mLoadedApk;
@@ -260,47 +261,25 @@
@CallSuper
public void onConfigurationChanged(@NonNull Configuration newConfig) {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- ((ComponentCallbacks)callbacks[i]).onConfigurationChanged(newConfig);
- }
- }
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
}
@CallSuper
public void onLowMemory() {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- ((ComponentCallbacks)callbacks[i]).onLowMemory();
- }
- }
+ mCallbacksController.dispatchLowMemory();
}
@CallSuper
public void onTrimMemory(int level) {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- Object c = callbacks[i];
- if (c instanceof ComponentCallbacks2) {
- ((ComponentCallbacks2)c).onTrimMemory(level);
- }
- }
- }
+ mCallbacksController.dispatchTrimMemory(level);
}
public void registerComponentCallbacks(ComponentCallbacks callback) {
- synchronized (mComponentCallbacks) {
- mComponentCallbacks.add(callback);
- }
+ mCallbacksController.registerCallbacks(callback);
}
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
- synchronized (mComponentCallbacks) {
- mComponentCallbacks.remove(callback);
- }
+ mCallbacksController.unregisterCallbacks(callback);
}
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
@@ -575,16 +554,6 @@
}
}
- private Object[] collectComponentCallbacks() {
- Object[] callbacks = null;
- synchronized (mComponentCallbacks) {
- if (mComponentCallbacks.size() > 0) {
- callbacks = mComponentCallbacks.toArray();
- }
- }
- return callbacks;
- }
-
@UnsupportedAppUsage
private Object[] collectActivityLifecycleCallbacks() {
Object[] callbacks = null;
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 3bfddf7..e5a969a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -244,6 +244,8 @@
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
boolean isAssistDataAllowedOnCurrentActivity();
+ boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
+ in String callingPackageName);
/**
* Notify the system that the keyguard is going away.
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index b6d25cf..4326c2d 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -133,6 +133,42 @@
*/
public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm";
+ /**
+ *
+ * Password lock type, see {@link #setLock}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PASSWORD = 0;
+
+ /**
+ *
+ * Pin lock type, see {@link #setLock}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PIN = 1;
+
+ /**
+ *
+ * Pattern lock type, see {@link #setLock}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PATTERN = 2;
+
+ /**
+ * Available lock types
+ */
+ @IntDef({
+ PASSWORD,
+ PIN,
+ PATTERN
+ })
+ @interface LockTypes {}
/**
* Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
@@ -695,7 +731,7 @@
PasswordMetrics adminMetrics =
devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId());
// Check if the password fits the mold of a pin or pattern.
- boolean isPinOrPattern = lockType != LockTypes.PASSWORD;
+ boolean isPinOrPattern = lockType != PASSWORD;
return PasswordMetrics.validatePassword(
adminMetrics, complexity, isPinOrPattern, password).size() == 0;
@@ -759,7 +795,7 @@
boolean success = false;
try {
switch (lockType) {
- case LockTypes.PASSWORD:
+ case PASSWORD:
CharSequence passwordStr = new String(password, Charset.forName("UTF-8"));
lockPatternUtils.setLockCredential(
LockscreenCredential.createPassword(passwordStr),
@@ -767,7 +803,7 @@
userId);
success = true;
break;
- case LockTypes.PIN:
+ case PIN:
CharSequence pinStr = new String(password);
lockPatternUtils.setLockCredential(
LockscreenCredential.createPin(pinStr),
@@ -775,7 +811,7 @@
userId);
success = true;
break;
- case LockTypes.PATTERN:
+ case PATTERN:
List<LockPatternView.Cell> pattern =
LockPatternUtils.byteArrayToPattern(password);
lockPatternUtils.setLockCredential(
@@ -796,18 +832,4 @@
}
return success;
}
-
- /**
- * Available lock types
- */
- @IntDef({
- LockTypes.PASSWORD,
- LockTypes.PIN,
- LockTypes.PATTERN
- })
- @interface LockTypes {
- int PASSWORD = 0;
- int PIN = 1;
- int PATTERN = 2;
- }
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f4e214c..4eda6fe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -20,8 +20,6 @@
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
import static java.util.Objects.requireNonNull;
import android.annotation.AttrRes;
@@ -1207,9 +1205,16 @@
public static final String EXTRA_PICTURE = "android.picture";
/**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown in {@link BigPictureStyle} expanded notifications, supplied to
+ * {@link BigPictureStyle#bigPicture(Icon)}.
+ */
+ public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
+
+ /**
* {@link #extras} key: this is a content description of the big picture supplied from
* {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
- * {@link BigPictureStyle#bigPictureContentDescription(CharSequence)}.
+ * {@link BigPictureStyle#setContentDescription(CharSequence)}.
*/
public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
"android.pictureContentDescription";
@@ -2668,6 +2673,14 @@
}
}
+ private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
+ if (icon == null) return;
+ final int iconType = icon.getType();
+ if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
+ visitor.accept(icon.getUri());
+ }
+ }
+
/**
* Note all {@link Uri} that are referenced internally, with the expectation
* that Uri permission grants will need to be issued to ensure the recipient
@@ -2683,7 +2696,19 @@
if (bigContentView != null) bigContentView.visitUris(visitor);
if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
+ visitIconUri(visitor, mSmallIcon);
+ visitIconUri(visitor, mLargeIcon);
+
+ if (actions != null) {
+ for (Action action : actions) {
+ visitIconUri(visitor, action.getIcon());
+ }
+ }
+
if (extras != null) {
+ visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON));
+
// NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
// String representation of a Uri, but the previous implementation (and unit test) of
// this method has always treated it as a Uri object. Given the inconsistency,
@@ -2702,14 +2727,12 @@
ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
if (people != null && !people.isEmpty()) {
for (Person p : people) {
- if (p.getIconUri() != null) {
- visitor.accept(p.getIconUri());
- }
+ visitor.accept(p.getIconUri());
}
}
final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON);
- if (person != null && person.getIconUri() != null) {
+ if (person != null) {
visitor.accept(person.getIconUri());
}
}
@@ -2722,7 +2745,7 @@
visitor.accept(message.getDataUri());
Person senderPerson = message.getSenderPerson();
- if (senderPerson != null && senderPerson.getIconUri() != null) {
+ if (senderPerson != null) {
visitor.accept(senderPerson.getIconUri());
}
}
@@ -2735,19 +2758,15 @@
visitor.accept(message.getDataUri());
Person senderPerson = message.getSenderPerson();
- if (senderPerson != null && senderPerson.getIconUri() != null) {
+ if (senderPerson != null) {
visitor.accept(senderPerson.getIconUri());
}
}
}
}
- if (mBubbleMetadata != null && mBubbleMetadata.getIcon() != null) {
- final Icon icon = mBubbleMetadata.getIcon();
- final int iconType = icon.getType();
- if (iconType == TYPE_URI_ADAPTIVE_BITMAP || iconType == TYPE_URI) {
- visitor.accept(icon.getUri());
- }
+ if (mBubbleMetadata != null) {
+ visitIconUri(visitor, mBubbleMetadata.getIcon());
}
}
@@ -3680,8 +3699,6 @@
private int mTextColorsAreForBackground = COLOR_INVALID;
private int mPrimaryTextColor = COLOR_INVALID;
private int mSecondaryTextColor = COLOR_INVALID;
- private int mBackgroundColor = COLOR_INVALID;
- private int mForegroundColor = COLOR_INVALID;
private boolean mRebuildStyledRemoteViews;
private boolean mTintActionButtons;
@@ -5020,7 +5037,8 @@
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
p.headerless(resId == getBaseLayoutResource()
- || resId == getHeadsUpBaseLayoutResource());
+ || resId == getHeadsUpBaseLayoutResource()
+ || resId == R.layout.notification_template_material_media);
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
@@ -5071,7 +5089,7 @@
}
private CharSequence processTextSpans(CharSequence text) {
- if (hasForegroundColor() || mInNightMode) {
+ if (mInNightMode) {
return ContrastColorUtil.clearColorSpans(text);
}
return text;
@@ -5082,10 +5100,6 @@
contentView.setTextColor(id, getPrimaryTextColor(p));
}
- private boolean hasForegroundColor() {
- return mForegroundColor != COLOR_INVALID;
- }
-
/**
* @param p the template params to inflate this with
* @return the primary text color
@@ -5119,75 +5133,15 @@
|| mSecondaryTextColor == COLOR_INVALID
|| mTextColorsAreForBackground != backgroundColor) {
mTextColorsAreForBackground = backgroundColor;
- if (!hasForegroundColor() || !isColorized(p)) {
- mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
- backgroundColor, mInNightMode);
- mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
- backgroundColor, mInNightMode);
- if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
- mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- mPrimaryTextColor, backgroundColor, 4.5);
- mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- mSecondaryTextColor, backgroundColor, 4.5);
- }
- } else {
- double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
- double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
- double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
- backgroundColor);
- // We only respect the given colors if worst case Black or White still has
- // contrast
- boolean backgroundLight = backLum > textLum
- && satisfiesTextContrast(backgroundColor, Color.BLACK)
- || backLum <= textLum
- && !satisfiesTextContrast(backgroundColor, Color.WHITE);
- if (contrast < 4.5f) {
- if (backgroundLight) {
- mSecondaryTextColor = ContrastColorUtil.findContrastColor(
- mForegroundColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
- } else {
- mSecondaryTextColor =
- ContrastColorUtil.findContrastColorAgainstDark(
- mForegroundColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
- }
- } else {
- mPrimaryTextColor = mForegroundColor;
- mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
- mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
- : LIGHTNESS_TEXT_DIFFERENCE_DARK);
- if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
- backgroundColor) < 4.5f) {
- // oh well the secondary is not good enough
- if (backgroundLight) {
- mSecondaryTextColor = ContrastColorUtil.findContrastColor(
- mSecondaryTextColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- } else {
- mSecondaryTextColor
- = ContrastColorUtil.findContrastColorAgainstDark(
- mSecondaryTextColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- }
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, backgroundLight
- ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
- : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
- }
- }
+ mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+ backgroundColor, mInNightMode);
+ mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
+ backgroundColor, mInNightMode);
+ if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
+ mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ mPrimaryTextColor, backgroundColor, 4.5);
+ mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ mSecondaryTextColor, backgroundColor, 4.5);
}
}
}
@@ -5233,11 +5187,7 @@
result = new TemplateBindResult();
}
bindLargeIcon(contentView, p, result);
- if (p.mHeaderless) {
- // views in the headerless (collapsed) state
- result.mHeadingExtraMarginSet.applyToView(contentView,
- R.id.notification_headerless_view_column);
- } else {
+ if (!p.mHeaderless) {
// views in states with a header (big states)
result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
result.mTitleMarginSet.applyToView(contentView, R.id.title);
@@ -5257,6 +5207,8 @@
@NonNull TemplateBindResult result) {
final Resources resources = mContext.getResources();
final float density = resources.getDisplayMetrics().density;
+ final float iconMarginDp = resources.getDimension(
+ R.dimen.notification_right_icon_content_margin) / density;
final float contentMarginDp = resources.getDimension(
R.dimen.notification_content_margin_end) / density;
final float expanderSizeDp = resources.getDimension(
@@ -5278,7 +5230,7 @@
}
}
}
- final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp;
+ final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
result.setRightIconState(largeIconShown, viewWidthDp,
extraMarginEndDpIfVisible, expanderSizeDp);
}
@@ -5342,7 +5294,7 @@
private void bindHeaderChronometerAndTime(RemoteViews contentView,
StandardTemplateParams p, boolean hasTextToLeft) {
- if (showsTimeOrChronometer()) {
+ if (!p.mHideTime && showsTimeOrChronometer()) {
if (hasTextToLeft) {
contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
setTextViewColorSecondary(contentView, R.id.time_divider, p);
@@ -5373,6 +5325,9 @@
*/
private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
+ if (p.mHideSubText) {
+ return false;
+ }
CharSequence summaryText = p.summaryText;
if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
&& mStyle.hasSummaryInHeader()) {
@@ -5403,6 +5358,9 @@
*/
private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
+ if (p.mHideSubText) {
+ return false;
+ }
if (!TextUtils.isEmpty(p.headerTextSecondary)) {
contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
processLegacyText(p.headerTextSecondary)));
@@ -6633,7 +6591,7 @@
*/
private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) {
if (isColorized(p)) {
- return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
+ return getRawColor(p);
} else {
return COLOR_DEFAULT;
}
@@ -6661,21 +6619,6 @@
}
/**
- * Set a color palette to be used as the background and textColors
- *
- * @param backgroundColor the color to be used as the background
- * @param foregroundColor the color to be used as the foreground
- *
- * @hide
- */
- public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) {
- mBackgroundColor = backgroundColor;
- mForegroundColor = foregroundColor;
- mTextColorsAreForBackground = COLOR_INVALID;
- ensureColors(mParams.reset().fillTextsFrom(this));
- }
-
- /**
* Forces all styled remoteViews to be built from scratch and not use any cached
* RemoteViews.
* This is needed for legacy apps that are baking in their remoteviews into the
@@ -6731,24 +6674,14 @@
if (mLargeIcon != null || largeIcon != null) {
Resources resources = context.getResources();
Class<? extends Style> style = getNotificationStyle();
- int maxWidth = resources.getDimensionPixelSize(isLowRam
+ int maxSize = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_right_icon_size_low_ram
: R.dimen.notification_right_icon_size);
- int maxHeight = maxWidth;
- if (MediaStyle.class.equals(style)
- || DecoratedMediaCustomViewStyle.class.equals(style)) {
- maxHeight = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_media_image_max_height_low_ram
- : R.dimen.notification_media_image_max_height);
- maxWidth = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_media_image_max_width_low_ram
- : R.dimen.notification_media_image_max_width);
- }
if (mLargeIcon != null) {
- mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight);
+ mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
}
if (largeIcon != null) {
- largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight);
+ largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
}
}
reduceImageSizesForRemoteView(contentView, context, isLowRam);
@@ -6835,9 +6768,6 @@
* @hide
*/
public boolean isColorized() {
- if (isColorizedMedia()) {
- return true;
- }
return extras.getBoolean(EXTRA_COLORIZED)
&& (hasColorizedPermission() || isForegroundService());
}
@@ -6851,27 +6781,6 @@
}
/**
- * @return true if this notification is colorized and it is a media notification
- *
- * @hide
- */
- public boolean isColorizedMedia() {
- Class<? extends Style> style = getNotificationStyle();
- if (MediaStyle.class.equals(style)) {
- Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED);
- if ((colorized == null || colorized) && hasMediaSession()) {
- return true;
- }
- } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
- if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) {
- return true;
- }
- }
- return false;
- }
-
-
- /**
* @return true if this is a media notification
*
* @hide
@@ -7159,15 +7068,6 @@
/**
* @hide
- * @return true if the style positions the progress bar on the second line; false if the
- * style hides the progress bar
- */
- protected boolean hasProgress() {
- return true;
- }
-
- /**
- * @hide
* @return Whether we should put the summary be put into the notification header
*/
public boolean hasSummaryInHeader() {
@@ -7231,7 +7131,7 @@
* @see Notification#bigContentView
*/
public static class BigPictureStyle extends Style {
- private Bitmap mPicture;
+ private Icon mPictureIcon;
private Icon mBigLargeIcon;
private boolean mBigLargeIconSet = false;
private CharSequence mPictureContentDescription;
@@ -7271,7 +7171,7 @@
* Set the content description of the big picture.
*/
@NonNull
- public BigPictureStyle bigPictureContentDescription(
+ public BigPictureStyle setContentDescription(
@Nullable CharSequence contentDescription) {
mPictureContentDescription = contentDescription;
return this;
@@ -7280,8 +7180,12 @@
/**
* @hide
*/
- public Bitmap getBigPicture() {
- return mPicture;
+ @Nullable
+ public Icon getBigPicture() {
+ if (mPictureIcon != null) {
+ return mPictureIcon;
+ }
+ return null;
}
/**
@@ -7289,7 +7193,16 @@
*/
@NonNull
public BigPictureStyle bigPicture(@Nullable Bitmap b) {
- mPicture = b;
+ mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
+ return this;
+ }
+
+ /**
+ * Provide the content Uri to be used as the payload for the BigPicture notification.
+ */
+ @NonNull
+ public BigPictureStyle bigPicture(@Nullable Icon icon) {
+ mPictureIcon = icon;
return this;
}
@@ -7331,10 +7244,8 @@
@Override
public void purgeResources() {
super.purgeResources();
- if (mPicture != null &&
- mPicture.isMutable() &&
- mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
- mPicture = mPicture.asShared();
+ if (mPictureIcon != null) {
+ mPictureIcon.convertToAshmem();
}
if (mBigLargeIcon != null) {
mBigLargeIcon.convertToAshmem();
@@ -7349,14 +7260,14 @@
super.reduceImageSizes(context);
Resources resources = context.getResources();
boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
- if (mPicture != null) {
+ if (mPictureIcon != null) {
int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_height_low_ram
: R.dimen.notification_big_picture_max_height);
int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_width_low_ram
: R.dimen.notification_big_picture_max_width);
- mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight);
+ mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
}
if (mBigLargeIcon != null) {
int rightIconSize = resources.getDimensionPixelSize(isLowRam
@@ -7371,12 +7282,12 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (mPicture == null || !mShowBigPictureWhenCollapsed) {
+ if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
return super.makeContentView(increasedHeight);
}
Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
- mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+ mBuilder.mN.mLargeIcon = mPictureIcon;
// The legacy largeIcon might not allow us to clear the image, as it's taken in
// replacement if the other one is null. Because we're restoring these legacy icons
// for old listeners, this is in general non-null.
@@ -7401,12 +7312,12 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (mPicture == null || !mShowBigPictureWhenCollapsed) {
+ if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
return super.makeHeadsUpContentView(increasedHeight);
}
Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
- mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+ mBuilder.mN.mLargeIcon = mPictureIcon;
// The legacy largeIcon might not allow us to clear the image, as it's taken in
// replacement if the other one is null. Because we're restoring these legacy icons
// for old listeners, this is in general non-null.
@@ -7463,7 +7374,7 @@
mBuilder.mN.largeIcon = largeIconLegacy;
}
- contentView.setImageViewBitmap(R.id.big_picture, mPicture);
+ contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
if (mPictureContentDescription != null) {
contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
@@ -7486,7 +7397,17 @@
mPictureContentDescription);
}
extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
- extras.putParcelable(EXTRA_PICTURE, mPicture);
+
+ // If the icon contains a bitmap, use the old extra so that listeners which look for
+ // that extra can still find the picture. Don't include the new extra in that case,
+ // to avoid duplicating data.
+ if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) {
+ extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
+ extras.putParcelable(EXTRA_PICTURE_ICON, null);
+ } else {
+ extras.putParcelable(EXTRA_PICTURE, null);
+ extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
+ }
}
/**
@@ -7507,7 +7428,16 @@
}
mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
- mPicture = extras.getParcelable(EXTRA_PICTURE);
+
+ // When this style adds a picture, we only add one of the keys. If both were added,
+ // it would most likely be a legacy app trying to override the picture in some way.
+ // Because of that case it's better to give precedence to the legacy field.
+ Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE);
+ if (bitmapPicture != null) {
+ mPictureIcon = Icon.createWithBitmap(bitmapPicture);
+ } else {
+ mPictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ }
}
/**
@@ -7529,20 +7459,32 @@
return true;
}
BigPictureStyle otherS = (BigPictureStyle) other;
- return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
+ return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
}
- private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) {
+ private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
if (a == b) {
return false;
}
if (a == null || b == null) {
return true;
}
- return a.getWidth() != b.getWidth()
- || a.getHeight() != b.getHeight()
- || a.getConfig() != b.getConfig()
- || a.getGenerationId() != b.getGenerationId();
+ if (a.sameAs(b)) {
+ return false;
+ }
+ final int aType = a.getType();
+ if (aType != b.getType()) {
+ return true;
+ }
+ if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
+ final Bitmap aBitmap = a.getBitmap();
+ final Bitmap bBitmap = b.getBitmap();
+ return aBitmap.getWidth() != bBitmap.getWidth()
+ || aBitmap.getHeight() != bBitmap.getHeight()
+ || aBitmap.getConfig() != bBitmap.getConfig()
+ || aBitmap.getGenerationId() != bBitmap.getGenerationId();
+ }
+ return true;
}
}
@@ -8978,7 +8920,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- return makeMediaContentView();
+ return makeMediaContentView(null /* customContent */);
}
/**
@@ -8986,7 +8928,7 @@
*/
@Override
public RemoteViews makeBigContentView() {
- return makeMediaBigContentView();
+ return makeMediaBigContentView(null /* customContent */);
}
/**
@@ -8994,7 +8936,7 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- return makeMediaContentView();
+ return makeMediaContentView(null /* customContent */);
}
/** @hide */
@@ -9064,88 +9006,72 @@
container.setContentDescription(buttonId, action.title);
}
- private RemoteViews makeMediaContentView() {
- StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
- .hideProgress(true)
- .fillTextsFrom(mBuilder);
- RemoteViews view = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_media, p,
- null /* result */);
-
+ /** @hide */
+ protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
final int numActions = mBuilder.mActions.size();
- final int numActionsToShow = mActionsToShowInCompact == null
- ? 0
- : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
+ final int numActionsToShow = Math.min(mActionsToShowInCompact == null
+ ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
if (numActionsToShow > numActions) {
throw new IllegalArgumentException(String.format(
"setShowActionsInCompactView: action %d out of bounds (max %d)",
numActions, numActions - 1));
}
+
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .hideTime(numActionsToShow > 1) // hide if actions wider than a large icon
+ .hideSubText(numActionsToShow > 1) // hide if actions wider than a large icon
+ .hideLargeIcon(numActionsToShow > 0) // large icon or actions; not both
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+ TemplateBindResult result = new TemplateBindResult();
+ RemoteViews template = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_media, p,
+ null /* result */);
+
for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
if (i < numActionsToShow) {
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
- bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
+ bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
} else {
- view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+ template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
- handleImage(view);
- // handle the content margin
- int endMargin = R.dimen.notification_content_margin_end;
- if (mBuilder.mN.hasLargeIcon()) {
- endMargin = R.dimen.notification_media_image_margin_end;
- }
- view.setViewLayoutMarginDimen(R.id.notification_main_column,
- RemoteViews.MARGIN_END, endMargin);
- return view;
+ // Prevent a swooping expand animation when there are no actions
+ boolean hasActions = numActionsToShow != 0;
+ template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
+
+ // Add custom view if provided by subclass.
+ buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
+ DevFlags.DECORATION_PARTIAL);
+ return template;
}
- private RemoteViews makeMediaBigContentView() {
+ /** @hide */
+ protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
- // Dont add an expanded view if there is no more content to be revealed
- int actionsInCompact = mActionsToShowInCompact == null
- ? 0
- : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
- if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
- return null;
- }
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG)
.hideProgress(true)
.fillTextsFrom(mBuilder);
- RemoteViews big = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_big_media, p , null /* result */);
+ TemplateBindResult result = new TemplateBindResult();
+ RemoteViews template = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_big_media, p , result);
for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
if (i < actionCount) {
- bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
+ bindMediaActionButton(template,
+ MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
} else {
- big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+ template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
- handleImage(big);
- return big;
- }
-
- private void handleImage(RemoteViews contentView) {
- if (mBuilder.mN.hasLargeIcon()) {
- contentView.setViewLayoutMarginDimen(R.id.title, RemoteViews.MARGIN_END, 0);
- contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0);
- }
- }
-
- /**
- * @hide
- */
- @Override
- protected boolean hasProgress() {
- return false;
+ buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
+ DevFlags.DECORATION_PARTIAL);
+ return template;
}
}
-
-
/**
* Helper class for generating large-format notifications that include a large image attachment.
*
@@ -9833,9 +9759,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
- return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
- mBuilder.mN.contentView);
+ return makeMediaContentView(mBuilder.mN.contentView);
}
/**
@@ -9843,24 +9767,10 @@
*/
@Override
public RemoteViews makeBigContentView() {
- RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
+ RemoteViews customContent = mBuilder.mN.bigContentView != null
? mBuilder.mN.bigContentView
: mBuilder.mN.contentView;
- return makeBigContentViewWithCustomContent(customRemoteView);
- }
-
- private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
- RemoteViews remoteViews = super.makeBigContentView();
- if (remoteViews != null) {
- return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
- customRemoteView);
- } else if (customRemoteView != mBuilder.mN.contentView){
- remoteViews = super.makeContentView(false /* increasedHeight */);
- return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
- customRemoteView);
- } else {
- return null;
- }
+ return makeMediaBigContentView(customContent);
}
/**
@@ -9868,10 +9778,10 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
+ RemoteViews customContent = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
- return makeBigContentViewWithCustomContent(customRemoteView);
+ return makeMediaBigContentView(customContent);
}
/**
@@ -9886,18 +9796,21 @@
return false;
}
- private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
- RemoteViews customContent) {
+ private RemoteViews buildIntoRemoteView(RemoteViews template, RemoteViews customContent,
+ boolean headerless) {
if (customContent != null) {
// Need to clone customContent before adding, because otherwise it can no longer be
// parceled independently of remoteViews.
customContent = customContent.clone();
customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
- remoteViews.removeAllViews(id);
- remoteViews.addView(id, customContent);
- remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
+ if (headerless) {
+ template.removeFromParent(R.id.notification_top_line);
+ }
+ template.removeAllViews(R.id.notification_main_column);
+ template.addView(R.id.notification_main_column, customContent);
+ template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
}
- return remoteViews;
+ return template;
}
}
@@ -12190,6 +12103,8 @@
boolean mHeaderless;
boolean mHideAppName;
boolean mHideTitle;
+ boolean mHideSubText;
+ boolean mHideTime;
boolean mHideActions;
boolean mHideProgress;
boolean mHideSnoozeButton;
@@ -12212,6 +12127,8 @@
mHeaderless = false;
mHideAppName = false;
mHideTitle = false;
+ mHideSubText = false;
+ mHideTime = false;
mHideActions = false;
mHideProgress = false;
mHideSnoozeButton = false;
@@ -12225,6 +12142,7 @@
summaryText = null;
headerTextSecondary = null;
maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
+ hideLargeIcon = false;
allowColorization = true;
mReduceHighlights = false;
return this;
@@ -12249,6 +12167,16 @@
return this;
}
+ public StandardTemplateParams hideSubText(boolean hideSubText) {
+ mHideSubText = hideSubText;
+ return this;
+ }
+
+ public StandardTemplateParams hideTime(boolean hideTime) {
+ mHideTime = hideTime;
+ return this;
+ }
+
final StandardTemplateParams hideActions(boolean hideActions) {
this.mHideActions = hideActions;
return this;
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 6b2e649..d640a6f 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -189,7 +189,7 @@
} else {
palette = Palette
.from(bitmap, new CelebiQuantizer())
- .maximumColorCount(256)
+ .maximumColorCount(5)
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
}
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index cbe2995..d44918c 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -20,8 +20,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -49,6 +52,8 @@
private final IWindowManager mWms;
private final WindowTokenClient mToken;
private boolean mListenerRegistered;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
/**
* Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -131,8 +136,24 @@
}
void destroy() {
+ mCallbacksController.clearCallbacks();
final ContextImpl impl = (ContextImpl) getBaseContext();
impl.scheduleFinalCleanup(getClass().getName(), "WindowContext");
Reference.reachabilityFence(this);
}
+
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
+ void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
+ }
}
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java
index 2298e84..82cef07 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/app/WindowTokenClient.java
@@ -61,7 +61,7 @@
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
- final Context context = mContextRef.get();
+ final WindowContext context = mContextRef.get();
if (context == null) {
return;
}
@@ -72,6 +72,8 @@
if (displayChanged || configChanged) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
+ ActivityThread.currentActivityThread().getHandler().post(
+ () -> context.dispatchConfigurationChanged(newConfig));
}
if (displayChanged) {
context.updateDisplay(newDisplayId);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 426159f..117df02 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9990,26 +9990,24 @@
}
/**
- * Sets whether 5g slicing is enabled on the work profile.
+ * Sets whether enterprise network preference is enabled on the work profile.
*
- * Slicing allows operators to virtually divide their networks in portions and use different
- * portions for specific use cases; for example, a corporation can have a deal/agreement with
- * a carrier that all of its employees’ devices use data on a slice dedicated for enterprise
- * use.
+ * For example, a corporation can have a deal/agreement with a carrier that all of its
+ * employees’ devices use data on a network preference dedicated for enterprise use.
*
- * By default, 5g slicing is enabled on the work profile on supported carriers and devices.
- * Admins can explicitly disable it with this API.
+ * By default, enterprise network preference is enabled on the work profile on supported
+ * carriers and devices. Admins can explicitly disable it with this API.
*
* <p>This method can only be called by the profile owner of a managed profile.
*
- * @param enabled whether 5g Slice should be enabled.
+ * @param enabled whether enterprise network preference should be enabled.
* @throws SecurityException if the caller is not the profile owner.
**/
- public void setNetworkSlicingEnabled(boolean enabled) {
- throwIfParentInstance("setNetworkSlicingEnabled");
+ public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) {
+ throwIfParentInstance("setEnterpriseNetworkPreferenceEnabled");
if (mService != null) {
try {
- mService.setNetworkSlicingEnabled(enabled);
+ mService.setEnterpriseNetworkPreferenceEnabled(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10017,20 +10015,20 @@
}
/**
- * Indicates whether 5g slicing is enabled.
+ * Indicates whether whether enterprise network preference is enabled.
*
* <p>This method can be called by the profile owner of a managed profile.
*
- * @return whether 5g Slice is enabled.
+ * @return whether whether enterprise network preference is enabled.
* @throws SecurityException if the caller is not the profile owner.
*/
- public boolean isNetworkSlicingEnabled() {
- throwIfParentInstance("isNetworkSlicingEnabled");
+ public boolean isEnterpriseNetworkPreferenceEnabled() {
+ throwIfParentInstance("isEnterpriseNetworkPreferenceEnabled");
if (mService == null) {
return false;
}
try {
- return mService.isNetworkSlicingEnabled(myUserId());
+ return mService.isEnterpriseNetworkPreferenceEnabled(myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7901791..0ad92b7 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -268,8 +268,8 @@
void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
boolean isSecondaryLockscreenEnabled(in UserHandle userHandle);
- void setNetworkSlicingEnabled(in boolean enabled);
- boolean isNetworkSlicingEnabled(int userHandle);
+ void setEnterpriseNetworkPreferenceEnabled(in boolean enabled);
+ boolean isEnterpriseNetworkPreferenceEnabled(int userHandle);
void setLockTaskPackages(in ComponentName who, in String[] packages);
String[] getLockTaskPackages(in ComponentName who);
diff --git a/core/java/android/app/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index 6759346..51430c0 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,12 +33,14 @@
* Name of this restore set. May be user generated, may simply be the name
* of the handset model, e.g. "T-Mobile G1".
*/
+ @Nullable
public String name;
/**
* Identifier of the device whose data this is. This will be as unique as
* is practically possible; for example, it might be an IMEI.
*/
+ @Nullable
public String device;
/**
@@ -47,15 +50,48 @@
*/
public long token;
+ /**
+ * Properties of the {@link BackupTransport} transport that was used to obtain the data in
+ * this restore set.
+ */
+ public final int backupTransportFlags;
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ */
public RestoreSet() {
// Leave everything zero / null
+ backupTransportFlags = 0;
}
- public RestoreSet(String _name, String _dev, long _token) {
- name = _name;
- device = _dev;
- token = _token;
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ *
+ * @param name The name of the restore set.
+ * @param device The name of the device where the restore data is coming from.
+ * @param token The unique identifier for the current restore set.
+ */
+ public RestoreSet(@Nullable String name, @Nullable String device, long token) {
+ this(name, device, token, /* backupTransportFlags */ 0);
+ }
+
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ *
+ * @param name The name of the restore set.
+ * @param device The name of the device where the restore data is coming from.
+ * @param token The unique identifier for the current restore set.
+ * @param backupTransportFlags Flags returned by {@link BackupTransport#getTransportFlags()}
+ * implementation of the backup transport used by the source device
+ * to create this restore set. See {@link BackupAgent} for possible
+ * flag values.
+ */
+ public RestoreSet(@Nullable String name, @Nullable String device, long token,
+ int backupTransportFlags) {
+ this.name = name;
+ this.device = device;
+ this.token = token;
+ this.backupTransportFlags = backupTransportFlags;
}
// Parcelable implementation
@@ -67,6 +103,7 @@
out.writeString(name);
out.writeString(device);
out.writeLong(token);
+ out.writeInt(backupTransportFlags);
}
public static final @android.annotation.NonNull Parcelable.Creator<RestoreSet> CREATOR
@@ -84,5 +121,6 @@
name = in.readString();
device = in.readString();
token = in.readLong();
+ backupTransportFlags = in.readInt();
}
}
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 5d50c5d7..ef92172 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -110,7 +110,9 @@
public long mTotalTimeForegroundServiceUsed;
/**
- * Last time this package's component is used, measured in milliseconds since the epoch.
+ * Last time this package's component is used by a client package, measured in milliseconds
+ * since the epoch. Note that component usage is only reported in certain cases (e.g. broadcast
+ * receiver, service, content provider).
* See {@link UsageEvents.Event#APP_COMPONENT_USED}
* @hide
*/
@@ -274,8 +276,10 @@
}
/**
- * Get the last time this package's component was used, measured in milliseconds since the
- * epoch.
+ * Get the last time this package's component was used by a client package, measured in
+ * milliseconds since the epoch. Note that component usage is only reported in certain cases
+ * (e.g. broadcast receiver, service, content provider).
+ * See {@link UsageEvents.Event#APP_COMPONENT_USED}
* @hide
*/
@SystemApi
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index 132cc40..de77848 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -24,6 +24,8 @@
import android.os.RemoteException;
import android.os.ServiceManager;
+import java.util.List;
+
/**
* This class provides an API surface for system apps to manipulate the app hibernation
* state of a package for the user provided in the context.
@@ -111,4 +113,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Get the hibernating packages for the user. This is equivalent to the list of packages for
+ * the user that return true for {@link #isHibernatingForUser}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+ public @NonNull List<String> getHibernatingPackagesForUser() {
+ try {
+ return mIAppHibernationService.getHibernatingPackagesForUser(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl
index 6a068ee..afdb3fe 100644
--- a/core/java/android/apphibernation/IAppHibernationService.aidl
+++ b/core/java/android/apphibernation/IAppHibernationService.aidl
@@ -25,4 +25,5 @@
void setHibernatingForUser(String packageName, int userId, boolean isHibernating);
boolean isHibernatingGlobally(String packageName);
void setHibernatingGlobally(String packageName, boolean isHibernating);
+ List<String> getHibernatingPackagesForUser(int userId);
}
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index a6b4b47..82da4fb 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -841,8 +841,8 @@
* Calling this method will trigger a full re-inflation of the App Widget.
*
* The color resources that can be overloaded are the ones whose name is prefixed with
- * {@code system_primary_}, {@code system_secondary_} or {@code system_neutral_}, for example
- * {@link android.R.color#system_primary_500}.
+ * {@code system_neutral} or {@code system_accent}, for example
+ * {@link android.R.color#system_neutral1_500}.
*/
public void setColorResources(@NonNull SparseIntArray colorMapping) {
mColorResources = RemoteViews.ColorResources.create(mContext, colorMapping);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8d41572..a3d19ca 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -52,6 +52,8 @@
import android.util.Log;
import android.util.Pair;
+import com.android.internal.util.Preconditions;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -3152,6 +3154,25 @@
return true;
}
+ /**
+ * Determines whether a String Bluetooth address, such as "00:43:A8:23:10:F0"
+ * is a RANDOM STATIC address.
+ *
+ * RANDOM STATIC: (addr & 0b11) == 0b11
+ * RANDOM RESOLVABLE: (addr & 0b11) == 0b10
+ * RANDOM non-RESOLVABLE: (addr & 0b11) == 0b00
+ *
+ * @param address Bluetooth address as string
+ * @return true if the 2 Least Significant Bits of the address equals 0b11.
+ *
+ * @hide
+ */
+ public static boolean isAddressRandomStatic(@NonNull String address) {
+ Preconditions.checkNotNull(address);
+ return checkBluetoothAddress(address)
+ && (Integer.parseInt(address.split(":")[5], 16) & 0b11) == 0b11;
+ }
+
@UnsupportedAppUsage
/*package*/ IBluetoothManager getBluetoothManager() {
return mManagerService;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 07dbdce..8908649 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1022,6 +1022,24 @@
public static final String EXTRA_MAS_INSTANCE =
"android.bluetooth.device.extra.MAS_INSTANCE";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "ADDRESS_TYPE_" },
+ value = {
+ /** Hardware MAC Address */
+ ADDRESS_TYPE_PUBLIC,
+ /** Address is either resolvable, non-resolvable or static.*/
+ ADDRESS_TYPE_RANDOM,
+ }
+ )
+ public @interface AddressType {}
+
+ /** Hardware MAC Address of the device */
+ public static final int ADDRESS_TYPE_PUBLIC = 0;
+ /** Address is either resolvable, non-resolvable or static. */
+ public static final int ADDRESS_TYPE_RANDOM = 1;
+
/**
* Lazy initialization. Guaranteed final after first object constructed, or
* getService() called.
@@ -1030,6 +1048,7 @@
private static volatile IBluetooth sService;
private final String mAddress;
+ @AddressType private final int mAddressType;
/*package*/
@UnsupportedAppUsage
@@ -1084,6 +1103,7 @@
}
mAddress = address;
+ mAddressType = ADDRESS_TYPE_PUBLIC;
}
@Override
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 51f63f7..7459f87 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -16,15 +16,19 @@
package android.bluetooth.le;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevice.AddressType;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.List;
@@ -53,6 +57,11 @@
@Nullable
private final String mDeviceAddress;
+ private final @AddressType int mAddressType;
+
+ @Nullable
+ private final byte[] mIrk;
+
@Nullable
private final ParcelUuid mServiceUuid;
@Nullable
@@ -79,12 +88,12 @@
/** @hide */
public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
-
private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
ParcelUuid uuidMask, ParcelUuid solicitationUuid,
ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
byte[] serviceData, byte[] serviceDataMask,
- int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
+ int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
+ @AddressType int addressType, @Nullable byte[] irk) {
mDeviceName = name;
mServiceUuid = uuid;
mServiceUuidMask = uuidMask;
@@ -97,6 +106,8 @@
mManufacturerId = manufacturerId;
mManufacturerData = manufacturerData;
mManufacturerDataMask = manufacturerDataMask;
+ mAddressType = addressType;
+ mIrk = irk;
}
@Override
@@ -280,6 +291,23 @@
return mDeviceAddress;
}
+ /**
+ * @hide
+ */
+ @SystemApi
+ public @AddressType int getAddressType() {
+ return mAddressType;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public byte[] getIrk() {
+ return mIrk;
+ }
+
@Nullable
public byte[] getServiceData() {
return mServiceData;
@@ -516,8 +544,16 @@
*/
public static final class Builder {
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final int LEN_IRK_OCTETS = 16;
+
private String mDeviceName;
private String mDeviceAddress;
+ private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
+ private byte[] mIrk;
private ParcelUuid mServiceUuid;
private ParcelUuid mUuidMask;
@@ -546,14 +582,130 @@
*
* @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
* format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
- * BluetoothAdapter#checkBluetoothAddress}.
+ * BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to {@link
+ * BluetoothDevice#ADDRESS_TYPE_PUBLIC}
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
*/
public Builder setDeviceAddress(String deviceAddress) {
- if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
+ return setDeviceAddress(mDeviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
+ }
+
+ /**
+ * Set filter on Address with AddressType
+ *
+ * <p>This key is used to resolve a private address from a public address.
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}. May be any type of address.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length
+ * @throws NullPointerException if {@code deviceAddress} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public Builder setDeviceAddress(@NonNull String deviceAddress,
+ @AddressType int addressType) {
+ return setDeviceAddressInternal(deviceAddress, addressType, null);
+ }
+
+ /**
+ * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+ *
+ * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from
+ * a PRIVATE_ADDRESS type.
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}. This Address type must only be PUBLIC OR RANDOM
+ * STATIC.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+ * @param irk non-null byte array representing the Identity Resolving Key
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException if the {@code irk} is invalid length.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not
+ * PUBLIC or RANDOM STATIC when an IRK is present.
+ * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public Builder setDeviceAddress(@NonNull String deviceAddress,
+ @AddressType int addressType,
+ @NonNull byte[] irk) {
+ Preconditions.checkNotNull(irk);
+ if (irk.length != LEN_IRK_OCTETS) {
+ throw new IllegalArgumentException("'irk' is invalid length!");
+ }
+ return setDeviceAddressInternal(deviceAddress, addressType, irk);
+ }
+
+ /**
+ * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+ *
+ * <p>Internal setter for the device address
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * @param irk non-null byte array representing the Identity Resolving Address; nullable
+ * internally.
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length.
+ * @throws NullPointerException if {@code deviceAddress} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ private Builder setDeviceAddressInternal(@NonNull String deviceAddress,
+ @AddressType int addressType,
+ @Nullable byte[] irk) {
+
+ // Make sure our deviceAddress is valid!
+ Preconditions.checkNotNull(deviceAddress);
+ if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
throw new IllegalArgumentException("invalid device address " + deviceAddress);
}
+
+ // Verify type range
+ if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC
+ || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+ throw new IllegalArgumentException("'addressType' is invalid!");
+ }
+
+ // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address.
+ if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+ // Don't want a bad combination of address and irk!
+ if (irk != null) {
+ // Since there are 3 possible RANDOM subtypes we must check to make sure
+ // the correct type of address is used.
+ if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) {
+ throw new IllegalArgumentException(
+ "Invalid combination: IRK requires either a PUBLIC or "
+ + "RANDOM (STATIC) Address");
+ }
+ }
+ }
+
+ // PUBLIC doesn't require extra work
+ // Without an IRK any address may be accepted
+
mDeviceAddress = deviceAddress;
+ mAddressType = addressType;
+ mIrk = irk;
return this;
}
@@ -727,7 +879,8 @@
mServiceUuid, mUuidMask, mServiceSolicitationUuid,
mServiceSolicitationUuidMask,
mServiceDataUuid, mServiceData, mServiceDataMask,
- mManufacturerId, mManufacturerData, mManufacturerDataMask);
+ mManufacturerId, mManufacturerData, mManufacturerDataMask,
+ mAddressType, mIrk);
}
}
}
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 504118e..368d1ee 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -52,6 +52,16 @@
public static final int SCAN_MODE_LOW_LATENCY = 2;
/**
+ * Perform Bluetooth LE scan in ambient discovery mode. This mode has lower duty cycle and more
+ * aggressive scan interval than balanced mode that provides a good trade-off between scan
+ * latency and power consumption.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3;
+
+ /**
* Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
* If no filter is active, all advertisement packets are reported.
*/
@@ -276,10 +286,17 @@
* @throws IllegalArgumentException If the {@code scanMode} is invalid.
*/
public Builder setScanMode(int scanMode) {
- if (scanMode < SCAN_MODE_OPPORTUNISTIC || scanMode > SCAN_MODE_LOW_LATENCY) {
- throw new IllegalArgumentException("invalid scan mode " + scanMode);
+ switch (scanMode) {
+ case SCAN_MODE_OPPORTUNISTIC:
+ case SCAN_MODE_LOW_POWER:
+ case SCAN_MODE_BALANCED:
+ case SCAN_MODE_LOW_LATENCY:
+ case SCAN_MODE_AMBIENT_DISCOVERY:
+ mScanMode = scanMode;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid scan mode " + scanMode);
}
- mScanMode = scanMode;
return this;
}
diff --git a/core/java/android/content/ComponentCallbacksController.java b/core/java/android/content/ComponentCallbacksController.java
new file mode 100644
index 0000000..a81aaf7
--- /dev/null
+++ b/core/java/android/content/ComponentCallbacksController.java
@@ -0,0 +1,127 @@
+/*
+ * 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.content;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A helper class to manage {@link ComponentCallbacks} and {@link ComponentCallbacks2}, such as
+ * registering ,unregistering {@link ComponentCallbacks} and sending callbacks to all registered
+ * {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ * @see ComponentCallbacks
+ * @see ComponentCallbacks2
+ *
+ * @hide
+ */
+public class ComponentCallbacksController {
+ @GuardedBy("mLock")
+ private List<ComponentCallbacks> mComponentCallbacks;
+
+ private final Object mLock = new Object();
+
+ /**
+ * Register the {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ */
+ public void registerCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null) {
+ mComponentCallbacks = new ArrayList<>();
+ }
+ mComponentCallbacks.add(callbacks);
+ }
+ }
+
+ /**
+ * Unregister the {@link ComponentCallbacks}.
+ *
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ */
+ public void unregisterCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ mComponentCallbacks.remove(callbacks);
+ }
+ }
+
+ /**
+ * Clear all registered {@link ComponentCallbacks}.
+ * It is useful when the associated {@link Context} is going to be released.
+ */
+ public void clearCallbacks() {
+ synchronized (mLock) {
+ if (mComponentCallbacks != null) {
+ mComponentCallbacks.clear();
+ }
+ }
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onConfigurationChanged(Configuration)} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ forAllComponentCallbacks(callbacks -> callbacks.onConfigurationChanged(newConfig));
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onLowMemory()} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchLowMemory() {
+ forAllComponentCallbacks(ComponentCallbacks::onLowMemory);
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks2#onTrimMemory(int)} to all registered
+ * {@link ComponentCallbacks2}.
+ */
+ public void dispatchTrimMemory(int level) {
+ forAllComponentCallbacks(callbacks -> {
+ if (callbacks instanceof ComponentCallbacks2) {
+ ((ComponentCallbacks2) callbacks).onTrimMemory(level);
+ }
+ });
+ }
+
+ private void forAllComponentCallbacks(Consumer<ComponentCallbacks> callbacksConsumer) {
+ final ComponentCallbacks[] callbacksArray;
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ callbacksArray = new ComponentCallbacks[mComponentCallbacks.size()];
+ mComponentCallbacks.toArray(callbacksArray);
+ }
+ for (ComponentCallbacks callbacks : callbacksArray) {
+ callbacksConsumer.accept(callbacks);
+ }
+ }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 64ca92f..92ff640 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -655,12 +655,21 @@
/**
* Add a new {@link ComponentCallbacks} to the base application of the
* Context, which will be called at the same times as the ComponentCallbacks
- * methods of activities and other components are called. Note that you
+ * methods of activities and other components are called. Note that you
* <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
* appropriate in the future; this will not be removed for you.
+ * <p>
+ * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created
+ * via {@link #createWindowContext(int, Bundle)} or
+ * {@link #createWindowContext(Display, int, Bundle)} will receive
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather
+ * than its base application. It is helpful if you want to handle UI components that
+ * associated with the Window Context when the Window Context has configuration changes.</p>
*
* @param callback The interface to call. This can be either a
* {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+ *
+ * @see Context#createWindowContext(int, Bundle)
*/
public void registerComponentCallbacks(ComponentCallbacks callback) {
getApplicationContext().registerComponentCallbacks(callback);
@@ -6358,6 +6367,16 @@
* windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
* </pre>
* <p>
+ * After {@link Build.VERSION_CODES#S}, window context provides the capability to listen to its
+ * {@link Configuration} changes by calling
+ * {@link #registerComponentCallbacks(ComponentCallbacks)}, while other kinds of {@link Context}
+ * will register the {@link ComponentCallbacks} to {@link #getApplicationContext() its
+ * Application context}. Note that window context only propagate
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback.
+ * {@link ComponentCallbacks#onLowMemory()} or other callbacks in {@link ComponentCallbacks2}
+ * won't be invoked.
+ * </p>
+ * <p>
* Note that using {@link android.app.Application} or {@link android.app.Service} context for
* UI-related queries may result in layout or continuity issues on devices with variable screen
* sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index a2880df..614143e 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -44,9 +44,12 @@
},
{
"include-filter": "android.content.ContextTest"
+ },
+ {
+ "include-filter": "android.content.ComponentCallbacksControllerTest"
}
],
- "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"]
+ "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
}
]
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 5af3b5a..c04d3be 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -40,6 +40,7 @@
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -327,6 +328,19 @@
return si;
}
+ /**
+ * @hide
+ */
+ @NonNull
+ public static List<GenericDocument> toGenericDocuments(
+ @NonNull final Collection<ShortcutInfo> shortcuts) {
+ final List<GenericDocument> docs = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfo si : shortcuts) {
+ docs.add(AppSearchShortcutInfo.instance(si));
+ }
+ return docs;
+ }
+
/** @hide */
@VisibleForTesting
public static class Builder extends GenericDocument.Builder<Builder> {
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index cfb6e1b..5a89708 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -321,6 +321,10 @@
return UserManager.isUserTypeManagedProfile(userType);
}
+ public boolean isCloneProfile() {
+ return UserManager.isUserTypeCloneProfile(userType);
+ }
+
@UnsupportedAppUsage
public boolean isEnabled() {
return (flags & FLAG_DISABLED) != FLAG_DISABLED;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 55e19f2..d187f60 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -341,11 +341,11 @@
}
/**
- * Retrieve the user selection data for the given {@param packageName} and the current user.
+ * Retrieve the user state for the given package and the {@link Context}'s user.
*
* @param packageName The app to query state for.
- * @return the user selection verification data for the given package for the current user, or
- * null if the package does not declare any HTTP/HTTPS domains.
+ * @return The user selection verification data for the given package for the user, or null if
+ * the package does not declare any HTTP/HTTPS domains.
*/
@Nullable
public DomainVerificationUserState getDomainVerificationUserState(
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3da1227..96e7d3b 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -326,6 +326,20 @@
* @hide
*/
public static final int ALLOWED_METERED_REASON_USER_EXEMPTED = 1 << 16;
+ /**
+ * Flag to indicate that app is exempt from certain metered network restrictions because of it
+ * being a system component.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_METERED_REASON_SYSTEM = 1 << 17;
+ /**
+ * Flag to indicate that app is exempt from certain metered network restrictions because of it
+ * being in the foreground.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_METERED_REASON_FOREGROUND = 1 << 18;
/** @hide */
public static final int ALLOWED_METERED_REASON_MASK = 0xffff0000;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fa6472e..4674aa2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -6030,7 +6030,7 @@
pw.print(":");
for (int it=0; it<types.size(); it++) {
pw.print(" ");
- pw.print(JobParameters.getReasonCodeDescription(types.keyAt(it)));
+ pw.print(JobParameters.getLegacyReasonCodeDescription(types.keyAt(it)));
pw.print("(");
pw.print(types.valueAt(it));
pw.print("x)");
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 087568d..34f2c103f 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -99,6 +99,8 @@
boolean someUserHasSeedAccount(in String accountName, in String accountType);
boolean isProfile(int userId);
boolean isManagedProfile(int userId);
+ boolean isCloneProfile(int userId);
+ boolean sharesMediaWithParent(int userId);
boolean isDemoUser(int userId);
boolean isPreCreated(int userId);
UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags,
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index b42a495..219912c 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -218,7 +218,7 @@
@Override
public boolean[] arePrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
boolean[] supported = new boolean[primitiveIds.length];
if (mVibratorManager == null) {
Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index b528eb1..841aad5 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -211,7 +211,7 @@
@Override
public boolean[] arePrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
boolean[] supported = new boolean[primitiveIds.length];
for (int i = 0; i < primitiveIds.length; i++) {
supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 5069e031..d4de4fa 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -25,6 +25,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
+import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -136,6 +137,16 @@
public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
/**
+ * User type representing a clone profile. Clone profile is a user profile type used to run
+ * second instance of an otherwise single user App (eg, messengers). Only the primary user
+ * is allowed to have a clone profile.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
+
+ /**
* User type representing a generic profile for testing purposes. Only on debuggable builds.
* @hide
*/
@@ -1984,6 +1995,14 @@
}
/**
+ * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
+ * @hide
+ */
+ public static boolean isUserTypeCloneProfile(String userType) {
+ return USER_TYPE_PROFILE_CLONE.equals(userType);
+ }
+
+ /**
* Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
* user type.
* @hide
@@ -2233,6 +2252,31 @@
}
/**
+ * Checks if the context user is a clone profile.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
+ * must be in the same profile group of the user.
+ *
+ * @return whether the context user is a clone profile.
+ *
+ * @see android.os.UserManager#USER_TYPE_PROFILE_CLONE
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @UserHandleAware
+ @SuppressAutoDoc
+ public boolean isCloneProfile() {
+ try {
+ return mService.isCloneProfile(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if the calling app is running as an ephemeral user.
*
* @return whether the caller is an ephemeral user.
@@ -4064,6 +4108,31 @@
}
/**
+ * If the user is a {@link UserManager#isProfile profile}, checks if the user
+ * shares media with its parent user (the user that created this profile).
+ * Returns false for any other type of user.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the
+ * caller must be in the same profile group as the user.
+ *
+ * @return true if the user shares media with its parent user, false otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @UserHandleAware
+ @SuppressAutoDoc
+ public boolean sharesMediaWithParent() {
+ try {
+ return mService.sharesMediaWithParent(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes a user and all associated data.
* @param userId the integer handle of the user.
* @hide
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 217f178..cec323f 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -21,6 +21,8 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.media.AudioAttributes;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import java.lang.annotation.Retention;
@@ -330,9 +332,9 @@
private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) {
if (effect != null) {
- if (mUsage == USAGE_UNKNOWN && effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- switch (prebaked.getId()) {
+ PrebakedSegment prebaked = extractPrebakedSegment(effect);
+ if (mUsage == USAGE_UNKNOWN && prebaked != null) {
+ switch (prebaked.getEffectId()) {
case VibrationEffect.EFFECT_CLICK:
case VibrationEffect.EFFECT_DOUBLE_CLICK:
case VibrationEffect.EFFECT_HEAVY_CLICK:
@@ -355,6 +357,20 @@
}
}
+ @Nullable
+ private PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.Composed) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ if (composed.getSegments().size() == 1) {
+ VibrationEffectSegment segment = composed.getSegments().get(0);
+ if (segment instanceof PrebakedSegment) {
+ return (PrebakedSegment) segment;
+ }
+ }
+ }
+ return null;
+ }
+
private void setUsage(@NonNull AudioAttributes audio) {
mOriginalAudioUsage = audio.getUsage();
switch (audio.getUsage()) {
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
index 89478fa..6311760 100644
--- a/core/java/android/os/VibrationEffect.aidl
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -17,4 +17,3 @@
package android.os;
parcelable VibrationEffect;
-parcelable VibrationEffect.Composition.PrimitiveEffect;
\ No newline at end of file
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0199fad..95b5e85 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -28,6 +28,10 @@
import android.hardware.vibrator.V1_0.EffectStrength;
import android.hardware.vibrator.V1_3.Effect;
import android.net.Uri;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.MathUtils;
import com.android.internal.util.Preconditions;
@@ -45,11 +49,6 @@
* These effects may be any number of things, from single shot vibrations to complex waveforms.
*/
public abstract class VibrationEffect implements Parcelable {
- private static final int PARCEL_TOKEN_ONE_SHOT = 1;
- private static final int PARCEL_TOKEN_WAVEFORM = 2;
- private static final int PARCEL_TOKEN_EFFECT = 3;
- private static final int PARCEL_TOKEN_COMPOSITION = 4;
-
// Stevens' coefficient to scale the perceived vibration intensity.
private static final float SCALE_GAMMA = 0.65f;
@@ -181,9 +180,7 @@
* @return The desired effect.
*/
public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
- VibrationEffect effect = new OneShot(milliseconds, amplitude);
- effect.validate();
- return effect;
+ return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */);
}
/**
@@ -243,7 +240,19 @@
* @return The desired effect.
*/
public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
- VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+ if (timings.length != amplitudes.length) {
+ throw new IllegalArgumentException(
+ "timing and amplitude arrays must be of equal length"
+ + " (timings.length=" + timings.length
+ + ", amplitudes.length=" + amplitudes.length + ")");
+ }
+ List<StepSegment> segments = new ArrayList<>();
+ for (int i = 0; i < timings.length; i++) {
+ float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE
+ ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE;
+ segments.add(new StepSegment(parsedAmplitude, (int) timings[i]));
+ }
+ VibrationEffect effect = new Composed(segments, repeat);
effect.validate();
return effect;
}
@@ -317,7 +326,8 @@
*/
@TestApi
public static VibrationEffect get(int effectId, boolean fallback) {
- VibrationEffect effect = new Prebaked(effectId, fallback, EffectStrength.MEDIUM);
+ VibrationEffect effect = new Composed(
+ new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM));
effect.validate();
return effect;
}
@@ -428,32 +438,28 @@
public abstract <T extends VibrationEffect> T scale(float scaleFactor);
/**
- * Scale given vibration intensity by the given factor.
+ * Applies given effect strength to prebaked effects represented by one of
+ * VibrationEffect.EFFECT_*.
*
- * @param amplitude amplitude of the effect, must be between 0 and MAX_AMPLITUDE
- * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
- * scale down the intensity, values larger than 1 will scale up
- *
+ * @param effectStrength new effect strength to be applied, one of
+ * VibrationEffect.EFFECT_STRENGTH_*.
+ * @return this if there is no change to this effect, or a copy of this effect with applied
+ * effect strength otherwise.
* @hide
*/
- protected static int scale(int amplitude, float scaleFactor) {
- if (amplitude == 0) {
- return 0;
- }
- int scaled = (int) (scale((float) amplitude / MAX_AMPLITUDE, scaleFactor) * MAX_AMPLITUDE);
- return MathUtils.constrain(scaled, 1, MAX_AMPLITUDE);
+ public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) {
+ return (T) this;
}
/**
* Scale given vibration intensity by the given factor.
*
- * @param intensity relative intensity of the effect, must be between 0 and 1
+ * @param intensity relative intensity of the effect, must be between 0 and 1
* @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
* scale down the intensity, values larger than 1 will scale up
- *
* @hide
*/
- protected static float scale(float intensity, float scaleFactor) {
+ public static float scale(float intensity, float scaleFactor) {
// Applying gamma correction to the scale factor, which is the same as encoding the input
// value, scaling it, then decoding the scaled value.
float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
@@ -516,545 +522,152 @@
}
}
- /** @hide */
+ /**
+ * Implementation of {@link VibrationEffect} described by a composition of one or more
+ * {@link VibrationEffectSegment}, with an optional index to represent repeating effects.
+ *
+ * @hide
+ */
@TestApi
- public static class OneShot extends VibrationEffect implements Parcelable {
- private final long mDuration;
- private final int mAmplitude;
+ public static final class Composed extends VibrationEffect {
+ private final ArrayList<VibrationEffectSegment> mSegments;
+ private final int mRepeatIndex;
- public OneShot(Parcel in) {
- mDuration = in.readLong();
- mAmplitude = in.readInt();
+ Composed(@NonNull Parcel in) {
+ this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt());
}
- public OneShot(long milliseconds, int amplitude) {
- mDuration = milliseconds;
- mAmplitude = amplitude;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- public int getAmplitude() {
- return mAmplitude;
+ Composed(@NonNull VibrationEffectSegment segment) {
+ this(Arrays.asList(segment), /* repeatIndex= */ -1);
}
/** @hide */
- @Override
- public OneShot scale(float scaleFactor) {
- if (scaleFactor == 1f || mAmplitude == DEFAULT_AMPLITUDE) {
- // Just return this if there's no scaling to be done or if amplitude is not yet set.
- return this;
- }
- return new OneShot(mDuration, scale(mAmplitude, scaleFactor));
+ public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) {
+ super();
+ mSegments = new ArrayList<>(segments);
+ mRepeatIndex = repeatIndex;
}
- /** @hide */
- @Override
- public OneShot resolve(int defaultAmplitude) {
- if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude <= 0) {
- throw new IllegalArgumentException(
- "amplitude must be between 1 and 255 inclusive (amplitude="
- + defaultAmplitude + ")");
- }
- if (mAmplitude == DEFAULT_AMPLITUDE) {
- return new OneShot(mDuration, defaultAmplitude);
- }
- return this;
- }
-
- /** @hide */
- @Override
- public void validate() {
- if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
- throw new IllegalArgumentException(
- "amplitude must either be DEFAULT_AMPLITUDE, "
- + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
- }
- if (mDuration <= 0) {
- throw new IllegalArgumentException(
- "duration must be positive (duration=" + mDuration + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.OneShot)) {
- return false;
- }
- VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
- return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result += 37 * (int) mDuration;
- result += 37 * mAmplitude;
- return result;
- }
-
- @Override
- public String toString() {
- return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_ONE_SHOT);
- out.writeLong(mDuration);
- out.writeInt(mAmplitude);
- }
-
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
- new Parcelable.Creator<OneShot>() {
- @Override
- public OneShot createFromParcel(Parcel in) {
- // Skip the type token
- in.readInt();
- return new OneShot(in);
- }
- @Override
- public OneShot[] newArray(int size) {
- return new OneShot[size];
- }
- };
- }
-
- /** @hide */
- @TestApi
- public static class Waveform extends VibrationEffect implements Parcelable {
- private final long[] mTimings;
- private final int[] mAmplitudes;
- private final int mRepeat;
-
- public Waveform(Parcel in) {
- this(in.createLongArray(), in.createIntArray(), in.readInt());
- }
-
- public Waveform(long[] timings, int[] amplitudes, int repeat) {
- mTimings = new long[timings.length];
- System.arraycopy(timings, 0, mTimings, 0, timings.length);
- mAmplitudes = new int[amplitudes.length];
- System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
- mRepeat = repeat;
- }
-
- public long[] getTimings() {
- return mTimings;
- }
-
- public int[] getAmplitudes() {
- return mAmplitudes;
+ @NonNull
+ public List<VibrationEffectSegment> getSegments() {
+ return mSegments;
}
public int getRepeatIndex() {
- return mRepeat;
+ return mRepeatIndex;
+ }
+
+ @Override
+ public void validate() {
+ int segmentCount = mSegments.size();
+ boolean hasNonZeroDuration = false;
+ boolean hasNonZeroAmplitude = false;
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = mSegments.get(i);
+ segment.validate();
+ // A segment with unknown duration = -1 still counts as a non-zero duration.
+ hasNonZeroDuration |= segment.getDuration() != 0;
+ hasNonZeroAmplitude |= segment.hasNonZeroAmplitude();
+ }
+ if (!hasNonZeroDuration) {
+ throw new IllegalArgumentException("at least one timing must be non-zero"
+ + " (segments=" + mSegments + ")");
+ }
+ if (!hasNonZeroAmplitude) {
+ throw new IllegalArgumentException("at least one amplitude must be non-zero"
+ + " (segments=" + mSegments + ")");
+ }
+ if (mRepeatIndex != -1) {
+ Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1,
+ "repeat index must be within the bounds of the segments (segments.length="
+ + segmentCount + ", index=" + mRepeatIndex + ")");
+ }
}
@Override
public long getDuration() {
- if (mRepeat >= 0) {
+ if (mRepeatIndex >= 0) {
return Long.MAX_VALUE;
}
- long duration = 0;
- for (long d : mTimings) {
- duration += d;
- }
- return duration;
- }
-
- /** @hide */
- @Override
- public Waveform scale(float scaleFactor) {
- if (scaleFactor == 1f) {
- // Just return this if there's no scaling to be done.
- return this;
- }
- boolean scaled = false;
- int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
- for (int i = 0; i < scaledAmplitudes.length; i++) {
- if (scaledAmplitudes[i] == DEFAULT_AMPLITUDE) {
- // Skip amplitudes that are not set.
- continue;
+ int segmentCount = mSegments.size();
+ long totalDuration = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ long segmentDuration = mSegments.get(i).getDuration();
+ if (segmentDuration < 0) {
+ return segmentDuration;
}
- scaled = true;
- scaledAmplitudes[i] = scale(scaledAmplitudes[i], scaleFactor);
+ totalDuration += segmentDuration;
}
- if (!scaled) {
- // Just return this if no scaling was done.
- return this;
- }
- return new Waveform(mTimings, scaledAmplitudes, mRepeat);
+ return totalDuration;
}
- /** @hide */
- @Override
- public Waveform resolve(int defaultAmplitude) {
- if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
- throw new IllegalArgumentException(
- "Amplitude is negative or greater than MAX_AMPLITUDE");
- }
- boolean resolved = false;
- int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
- for (int i = 0; i < resolvedAmplitudes.length; i++) {
- if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
- resolvedAmplitudes[i] = defaultAmplitude;
- resolved = true;
- }
- }
- if (!resolved) {
- return this;
- }
- return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
- }
-
- /** @hide */
- @Override
- public void validate() {
- if (mTimings.length != mAmplitudes.length) {
- throw new IllegalArgumentException(
- "timing and amplitude arrays must be of equal length"
- + " (timings.length=" + mTimings.length
- + ", amplitudes.length=" + mAmplitudes.length + ")");
- }
- if (!hasNonZeroEntry(mTimings)) {
- throw new IllegalArgumentException("at least one timing must be non-zero"
- + " (timings=" + Arrays.toString(mTimings) + ")");
- }
- for (long timing : mTimings) {
- if (timing < 0) {
- throw new IllegalArgumentException("timings must all be >= 0"
- + " (timings=" + Arrays.toString(mTimings) + ")");
- }
- }
- for (int amplitude : mAmplitudes) {
- if (amplitude < -1 || amplitude > 255) {
- throw new IllegalArgumentException(
- "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
- + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
- }
- }
- if (mRepeat < -1 || mRepeat >= mTimings.length) {
- throw new IllegalArgumentException(
- "repeat index must be within the bounds of the timings array"
- + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.Waveform)) {
- return false;
- }
- VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
- return Arrays.equals(mTimings, other.mTimings)
- && Arrays.equals(mAmplitudes, other.mAmplitudes)
- && mRepeat == other.mRepeat;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result += 37 * Arrays.hashCode(mTimings);
- result += 37 * Arrays.hashCode(mAmplitudes);
- result += 37 * mRepeat;
- return result;
- }
-
- @Override
- public String toString() {
- return "Waveform{mTimings=" + Arrays.toString(mTimings)
- + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
- + ", mRepeat=" + mRepeat
- + "}";
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_WAVEFORM);
- out.writeLongArray(mTimings);
- out.writeIntArray(mAmplitudes);
- out.writeInt(mRepeat);
- }
-
- private static boolean hasNonZeroEntry(long[] vals) {
- for (long val : vals) {
- if (val != 0) {
- return true;
- }
- }
- return false;
- }
-
-
- public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
- new Parcelable.Creator<Waveform>() {
- @Override
- public Waveform createFromParcel(Parcel in) {
- // Skip the type token
- in.readInt();
- return new Waveform(in);
- }
- @Override
- public Waveform[] newArray(int size) {
- return new Waveform[size];
- }
- };
- }
-
- /** @hide */
- @TestApi
- public static class Prebaked extends VibrationEffect implements Parcelable {
- private final int mEffectId;
- private final boolean mFallback;
- private final int mEffectStrength;
- @Nullable
- private final VibrationEffect mFallbackEffect;
-
- public Prebaked(Parcel in) {
- mEffectId = in.readInt();
- mFallback = in.readByte() != 0;
- mEffectStrength = in.readInt();
- mFallbackEffect = in.readParcelable(VibrationEffect.class.getClassLoader());
- }
-
- public Prebaked(int effectId, boolean fallback, int effectStrength) {
- mEffectId = effectId;
- mFallback = fallback;
- mEffectStrength = effectStrength;
- mFallbackEffect = null;
- }
-
- /** @hide */
- public Prebaked(int effectId, int effectStrength, @NonNull VibrationEffect fallbackEffect) {
- mEffectId = effectId;
- mFallback = true;
- mEffectStrength = effectStrength;
- mFallbackEffect = fallbackEffect;
- }
-
- public int getId() {
- return mEffectId;
- }
-
- /**
- * Whether the effect should fall back to a generic pattern if there's no hardware specific
- * implementation of it.
- */
- public boolean shouldFallback() {
- return mFallback;
- }
-
- @Override
- public long getDuration() {
- return -1;
- }
-
- /** @hide */
- @Override
- public Prebaked resolve(int defaultAmplitude) {
- if (mFallbackEffect != null) {
- VibrationEffect resolvedFallback = mFallbackEffect.resolve(defaultAmplitude);
- if (!mFallbackEffect.equals(resolvedFallback)) {
- return new Prebaked(mEffectId, mEffectStrength, resolvedFallback);
- }
- }
- return this;
- }
-
- /** @hide */
- @Override
- public Prebaked scale(float scaleFactor) {
- if (mFallbackEffect != null) {
- VibrationEffect scaledFallback = mFallbackEffect.scale(scaleFactor);
- if (!mFallbackEffect.equals(scaledFallback)) {
- return new Prebaked(mEffectId, mEffectStrength, scaledFallback);
- }
- }
- // Prebaked effect strength cannot be scaled with this method.
- return this;
- }
-
- /**
- * Set the effect strength.
- */
- public int getEffectStrength() {
- return mEffectStrength;
- }
-
- /**
- * Return the fallback effect, if set.
- *
- * @hide
- */
- @Nullable
- public VibrationEffect getFallbackEffect() {
- return mFallbackEffect;
- }
-
- private static boolean isValidEffectStrength(int strength) {
- switch (strength) {
- case EffectStrength.LIGHT:
- case EffectStrength.MEDIUM:
- case EffectStrength.STRONG:
- return true;
- default:
- return false;
- }
- }
-
- /** @hide */
- @Override
- public void validate() {
- switch (mEffectId) {
- case EFFECT_CLICK:
- case EFFECT_DOUBLE_CLICK:
- case EFFECT_TICK:
- case EFFECT_TEXTURE_TICK:
- case EFFECT_THUD:
- case EFFECT_POP:
- case EFFECT_HEAVY_CLICK:
- break;
- default:
- if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
- throw new IllegalArgumentException(
- "Unknown prebaked effect type (value=" + mEffectId + ")");
- }
- }
- if (!isValidEffectStrength(mEffectStrength)) {
- throw new IllegalArgumentException(
- "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.Prebaked)) {
- return false;
- }
- VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
- return mEffectId == other.mEffectId
- && mFallback == other.mFallback
- && mEffectStrength == other.mEffectStrength
- && Objects.equals(mFallbackEffect, other.mFallbackEffect);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mEffectId, mFallback, mEffectStrength, mFallbackEffect);
- }
-
- @Override
- public String toString() {
- return "Prebaked{mEffectId=" + effectIdToString(mEffectId)
- + ", mEffectStrength=" + effectStrengthToString(mEffectStrength)
- + ", mFallback=" + mFallback
- + ", mFallbackEffect=" + mFallbackEffect
- + "}";
- }
-
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_EFFECT);
- out.writeInt(mEffectId);
- out.writeByte((byte) (mFallback ? 1 : 0));
- out.writeInt(mEffectStrength);
- out.writeParcelable(mFallbackEffect, flags);
- }
-
- public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
- new Parcelable.Creator<Prebaked>() {
- @Override
- public Prebaked createFromParcel(Parcel in) {
- // Skip the type token
- in.readInt();
- return new Prebaked(in);
- }
- @Override
- public Prebaked[] newArray(int size) {
- return new Prebaked[size];
- }
- };
- }
-
- /** @hide */
- public static final class Composed extends VibrationEffect implements Parcelable {
- private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects;
-
- /**
- * @hide
- */
- @SuppressWarnings("unchecked")
- public Composed(@NonNull Parcel in) {
- this(in.readArrayList(Composed.class.getClassLoader()));
- }
-
- /**
- * @hide
- */
- public Composed(List<Composition.PrimitiveEffect> effects) {
- mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects));
- }
-
- /**
- * @hide
- */
@NonNull
- public List<Composition.PrimitiveEffect> getPrimitiveEffects() {
- return mPrimitiveEffects;
- }
-
@Override
- public long getDuration() {
- return -1;
+ public Composed resolve(int defaultAmplitude) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
+ }
+ if (resolvedSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
+ resolved.validate();
+ return resolved;
}
- /** @hide */
- @Override
- public VibrationEffect resolve(int defaultAmplitude) {
- // Primitive effects already have default primitive intensity set, so ignore this.
- return this;
- }
-
- /** @hide */
+ @NonNull
@Override
public Composed scale(float scaleFactor) {
- if (scaleFactor == 1f) {
- // Just return this if there's no scaling to be done.
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).scale(scaleFactor));
+ }
+ if (scaledSegments.equals(mSegments)) {
return this;
}
- final int primitiveCount = mPrimitiveEffects.size();
- List<Composition.PrimitiveEffect> scaledPrimitives = new ArrayList<>();
- for (int i = 0; i < primitiveCount; i++) {
- Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i);
- scaledPrimitives.add(new Composition.PrimitiveEffect(
- primitive.id, scale(primitive.scale, scaleFactor), primitive.delay));
- }
- return new Composed(scaledPrimitives);
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
}
- /** @hide */
+ @NonNull
@Override
- public void validate() {
- final int primitiveCount = mPrimitiveEffects.size();
- for (int i = 0; i < primitiveCount; i++) {
- Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i);
- Composition.checkPrimitive(primitive.id);
- Preconditions.checkArgumentInRange(primitive.scale, 0.0f, 1.0f, "scale");
- Preconditions.checkArgumentNonNegative(primitive.delay,
- "Primitive delay must be zero or positive");
+ public Composed applyEffectStrength(int effectStrength) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength));
}
+ if (scaledSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
}
@Override
- public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_COMPOSITION);
- out.writeList(mPrimitiveEffects);
+ public boolean equals(@Nullable Object o) {
+ if (!(o instanceof Composed)) {
+ return false;
+ }
+ Composed other = (Composed) o;
+ return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSegments, mRepeatIndex);
+ }
+
+ @Override
+ public String toString() {
+ return "Composed{segments=" + mSegments
+ + ", repeat=" + mRepeatIndex
+ + "}";
}
@Override
@@ -1063,34 +676,20 @@
}
@Override
- public boolean equals(@Nullable Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Composed composed = (Composed) o;
- return mPrimitiveEffects.equals(composed.mPrimitiveEffects);
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeList(mSegments);
+ out.writeInt(mRepeatIndex);
}
- @Override
- public int hashCode() {
- return Objects.hash(mPrimitiveEffects);
- }
-
- @Override
- public String toString() {
- return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}';
- }
-
- public static final @NonNull Parcelable.Creator<Composed> CREATOR =
- new Parcelable.Creator<Composed>() {
+ @NonNull
+ public static final Creator<Composed> CREATOR =
+ new Creator<Composed>() {
@Override
- public Composed createFromParcel(@NonNull Parcel in) {
- // Skip the type token
- in.readInt();
+ public Composed createFromParcel(Parcel in) {
return new Composed(in);
}
@Override
- @NonNull
public Composed[] newArray(int size) {
return new Composed[size];
}
@@ -1115,7 +714,7 @@
PRIMITIVE_LOW_TICK,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Primitive {}
+ public @interface PrimitiveType {}
/**
* No haptic effect. Used to generate extended delays between primitives.
@@ -1166,9 +765,10 @@
public static final int PRIMITIVE_LOW_TICK = 8;
- private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
+ private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+ private int mRepeatIndex = -1;
- Composition() { }
+ Composition() {}
/**
* Add a haptic primitive to the end of the current composition.
@@ -1181,9 +781,8 @@
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
*/
@NonNull
- public Composition addPrimitive(@Primitive int primitiveId) {
- addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
- return this;
+ public Composition addPrimitive(@PrimitiveType int primitiveId) {
+ return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
}
/**
@@ -1197,10 +796,9 @@
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
*/
@NonNull
- public Composition addPrimitive(@Primitive int primitiveId,
+ public Composition addPrimitive(@PrimitiveType int primitiveId,
@FloatRange(from = 0f, to = 1f) float scale) {
- addPrimitive(primitiveId, scale, /*delay*/ 0);
- return this;
+ return addPrimitive(primitiveId, scale, /*delay*/ 0);
}
/**
@@ -1213,9 +811,21 @@
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
*/
@NonNull
- public Composition addPrimitive(@Primitive int primitiveId,
+ public Composition addPrimitive(@PrimitiveType int primitiveId,
@FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
- mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay));
+ PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale,
+ delay);
+ primitive.validate();
+ return addSegment(primitive);
+ }
+
+ private Composition addSegment(VibrationEffectSegment segment) {
+ if (mRepeatIndex >= 0) {
+ throw new IllegalStateException(
+ "Composition already have a repeating effect so any new primitive would be"
+ + " unreachable.");
+ }
+ mSegments.add(segment);
return this;
}
@@ -1230,22 +840,13 @@
*/
@NonNull
public VibrationEffect compose() {
- if (mEffects.isEmpty()) {
+ if (mSegments.isEmpty()) {
throw new IllegalStateException(
"Composition must have at least one element to compose.");
}
- return new VibrationEffect.Composed(mEffects);
- }
-
- /**
- * @throws IllegalArgumentException throws if the primitive ID is not within the valid range
- * @hide
- *
- */
- static int checkPrimitive(int primitiveId) {
- Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LOW_TICK,
- "primitiveId");
- return primitiveId;
+ VibrationEffect effect = new Composed(mSegments, mRepeatIndex);
+ effect.validate();
+ return effect;
}
/**
@@ -1254,7 +855,7 @@
* @return The ID in a human readable format.
* @hide
*/
- public static String primitiveToString(@Primitive int id) {
+ public static String primitiveToString(@PrimitiveType int id) {
switch (id) {
case PRIMITIVE_NOOP:
return "PRIMITIVE_NOOP";
@@ -1278,90 +879,14 @@
return Integer.toString(id);
}
}
-
-
- /**
- * @hide
- */
- public static class PrimitiveEffect implements Parcelable {
- public int id;
- public float scale;
- public int delay;
-
- PrimitiveEffect(int id, float scale, int delay) {
- this.id = id;
- this.scale = scale;
- this.delay = delay;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(id);
- dest.writeFloat(scale);
- dest.writeInt(delay);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public String toString() {
- return "PrimitiveEffect{"
- + "id=" + primitiveToString(id)
- + ", scale=" + scale
- + ", delay=" + delay
- + '}';
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- PrimitiveEffect that = (PrimitiveEffect) o;
- return id == that.id
- && Float.compare(that.scale, scale) == 0
- && delay == that.delay;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, scale, delay);
- }
-
-
- public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR =
- new Parcelable.Creator<PrimitiveEffect>() {
- @Override
- public PrimitiveEffect createFromParcel(Parcel in) {
- return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt());
- }
- @Override
- public PrimitiveEffect[] newArray(int size) {
- return new PrimitiveEffect[size];
- }
- };
- }
}
- public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<VibrationEffect> CREATOR =
new Parcelable.Creator<VibrationEffect>() {
@Override
public VibrationEffect createFromParcel(Parcel in) {
- int token = in.readInt();
- if (token == PARCEL_TOKEN_ONE_SHOT) {
- return new OneShot(in);
- } else if (token == PARCEL_TOKEN_WAVEFORM) {
- return new Waveform(in);
- } else if (token == PARCEL_TOKEN_EFFECT) {
- return new Prebaked(in);
- } else if (token == PARCEL_TOKEN_COMPOSITION) {
- return new Composed(in);
- } else {
- throw new IllegalStateException(
- "Unexpected vibration event type token in parcel.");
- }
+ return new Composed(in);
}
@Override
public VibrationEffect[] newArray(int size) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b90d438..a0f70c8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -467,7 +467,7 @@
*/
@NonNull
public boolean[] arePrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
return new boolean[primitiveIds.length];
}
@@ -478,7 +478,7 @@
* @return Whether primitives effects are supported.
*/
public final boolean areAllPrimitivesSupported(
- @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
for (boolean supported : arePrimitivesSupported(primitiveIds)) {
if (!supported) {
return false;
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 3121b95..9c46bc9 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -153,7 +153,8 @@
* @param primitiveId Which primitives to query for.
* @return Whether the primitive is supported.
*/
- public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) {
+ public boolean isPrimitiveSupported(
+ @VibrationEffect.Composition.PrimitiveType int primitiveId) {
return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
&& mSupportedPrimitives.get(primitiveId, false);
}
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
new file mode 100644
index 0000000..78b4346
--- /dev/null
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -0,0 +1,184 @@
+/*
+ * 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a prebaked vibration effect.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrebakedSegment extends VibrationEffectSegment {
+ private final int mEffectId;
+ private final boolean mFallback;
+ private final int mEffectStrength;
+
+ PrebakedSegment(@NonNull Parcel in) {
+ mEffectId = in.readInt();
+ mFallback = in.readByte() != 0;
+ mEffectStrength = in.readInt();
+ }
+
+ /** @hide */
+ public PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength) {
+ mEffectId = effectId;
+ mFallback = shouldFallback;
+ mEffectStrength = effectStrength;
+ }
+
+ public int getEffectId() {
+ return mEffectId;
+ }
+
+ public int getEffectStrength() {
+ return mEffectStrength;
+ }
+
+ /** Return true if a fallback effect should be played if this effect is not supported. */
+ public boolean shouldFallback() {
+ return mFallback;
+ }
+
+ @Override
+ public long getDuration() {
+ return -1;
+ }
+
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public PrebakedSegment resolve(int defaultAmplitude) {
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PrebakedSegment scale(float scaleFactor) {
+ // Prebaked effect strength cannot be scaled with this method.
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PrebakedSegment applyEffectStrength(int effectStrength) {
+ if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
+ return new PrebakedSegment(mEffectId, mFallback, effectStrength);
+ }
+ return this;
+ }
+
+ private static boolean isValidEffectStrength(int strength) {
+ switch (strength) {
+ case VibrationEffect.EFFECT_STRENGTH_LIGHT:
+ case VibrationEffect.EFFECT_STRENGTH_MEDIUM:
+ case VibrationEffect.EFFECT_STRENGTH_STRONG:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void validate() {
+ switch (mEffectId) {
+ case VibrationEffect.EFFECT_CLICK:
+ case VibrationEffect.EFFECT_DOUBLE_CLICK:
+ case VibrationEffect.EFFECT_TICK:
+ case VibrationEffect.EFFECT_TEXTURE_TICK:
+ case VibrationEffect.EFFECT_THUD:
+ case VibrationEffect.EFFECT_POP:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ break;
+ default:
+ int[] ringtones = VibrationEffect.RINGTONES;
+ if (mEffectId < ringtones[0] || mEffectId > ringtones[ringtones.length - 1]) {
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect type (value=" + mEffectId + ")");
+ }
+ }
+ if (!isValidEffectStrength(mEffectStrength)) {
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (!(o instanceof PrebakedSegment)) {
+ return false;
+ }
+ PrebakedSegment other = (PrebakedSegment) o;
+ return mEffectId == other.mEffectId
+ && mFallback == other.mFallback
+ && mEffectStrength == other.mEffectStrength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEffectId, mFallback, mEffectStrength);
+ }
+
+ @Override
+ public String toString() {
+ return "Prebaked{effect=" + VibrationEffect.effectIdToString(mEffectId)
+ + ", strength=" + VibrationEffect.effectStrengthToString(mEffectStrength)
+ + ", fallback=" + mFallback
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_PREBAKED);
+ out.writeInt(mEffectId);
+ out.writeByte((byte) (mFallback ? 1 : 0));
+ out.writeInt(mEffectStrength);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<PrebakedSegment> CREATOR =
+ new Parcelable.Creator<PrebakedSegment>() {
+ @Override
+ public PrebakedSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new PrebakedSegment(in);
+ }
+
+ @Override
+ public PrebakedSegment[] newArray(int size) {
+ return new PrebakedSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
new file mode 100644
index 0000000..2ef29cb
--- /dev/null
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -0,0 +1,155 @@
+/*
+ * 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a primitive vibration effect after a
+ * specified delay and applying a given scale.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrimitiveSegment extends VibrationEffectSegment {
+ private final int mPrimitiveId;
+ private final float mScale;
+ private final int mDelay;
+
+ PrimitiveSegment(@NonNull Parcel in) {
+ this(in.readInt(), in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public PrimitiveSegment(int id, float scale, int delay) {
+ mPrimitiveId = id;
+ mScale = scale;
+ mDelay = delay;
+ }
+
+ public int getPrimitiveId() {
+ return mPrimitiveId;
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ public int getDelay() {
+ return mDelay;
+ }
+
+ @Override
+ public long getDuration() {
+ return -1;
+ }
+
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0.
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public PrimitiveSegment resolve(int defaultAmplitude) {
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PrimitiveSegment scale(float scaleFactor) {
+ return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor),
+ mDelay);
+ }
+
+ @NonNull
+ @Override
+ public PrimitiveSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public void validate() {
+ Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP,
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId");
+ Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale");
+ Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0");
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(PARCEL_TOKEN_PRIMITIVE);
+ dest.writeInt(mPrimitiveId);
+ dest.writeFloat(mScale);
+ dest.writeInt(mDelay);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Primitive{"
+ + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId)
+ + ", scale=" + mScale
+ + ", delay=" + mDelay
+ + '}';
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrimitiveSegment that = (PrimitiveSegment) o;
+ return mPrimitiveId == that.mPrimitiveId
+ && Float.compare(that.mScale, mScale) == 0
+ && mDelay == that.mDelay;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPrimitiveId, mScale, mDelay);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<PrimitiveSegment> CREATOR =
+ new Parcelable.Creator<PrimitiveSegment>() {
+ @Override
+ public PrimitiveSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new PrimitiveSegment(in);
+ }
+
+ @Override
+ public PrimitiveSegment[] newArray(int size) {
+ return new PrimitiveSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
new file mode 100644
index 0000000..61a5d6c
--- /dev/null
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -0,0 +1,152 @@
+/*
+ * 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude for a
+ * specified duration.
+ *
+ * @hide
+ */
+@TestApi
+public final class StepSegment extends VibrationEffectSegment {
+ private final float mAmplitude;
+ private final int mDuration;
+
+ StepSegment(@NonNull Parcel in) {
+ this(in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public StepSegment(float amplitude, int duration) {
+ mAmplitude = amplitude;
+ mDuration = duration;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof StepSegment)) {
+ return false;
+ }
+ StepSegment other = (StepSegment) o;
+ return Float.compare(mAmplitude, other.mAmplitude) == 0
+ && mDuration == other.mDuration;
+ }
+
+ public float getAmplitude() {
+ return mAmplitude;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later.
+ return Float.compare(mAmplitude, 0) != 0;
+ }
+
+ @Override
+ public void validate() {
+ Preconditions.checkArgumentNonnegative(mDuration,
+ "Durations must all be >= 0, got " + mDuration);
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+ Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude");
+ }
+ }
+
+ @NonNull
+ @Override
+ public StepSegment resolve(int defaultAmplitude) {
+ if (defaultAmplitude > VibrationEffect.MAX_AMPLITUDE || defaultAmplitude <= 0) {
+ throw new IllegalArgumentException(
+ "amplitude must be between 1 and 255 inclusive (amplitude="
+ + defaultAmplitude + ")");
+ }
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+ return this;
+ }
+ return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mDuration);
+ }
+
+ @NonNull
+ @Override
+ public StepSegment scale(float scaleFactor) {
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+ return this;
+ }
+ return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mDuration);
+ }
+
+ @NonNull
+ @Override
+ public StepSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAmplitude, mDuration);
+ }
+
+ @Override
+ public String toString() {
+ return "Step{amplitude=" + mAmplitude
+ + ", duration=" + mDuration
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_STEP);
+ out.writeFloat(mAmplitude);
+ out.writeInt(mDuration);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<StepSegment> CREATOR =
+ new Parcelable.Creator<StepSegment>() {
+ @Override
+ public StepSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new StepSegment(in);
+ }
+
+ @Override
+ public StepSegment[] newArray(int size) {
+ return new StepSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
new file mode 100644
index 0000000..3dc9e12
--- /dev/null
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -0,0 +1,114 @@
+/*
+ * 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.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+/**
+ * Representation of a single segment of a {@link VibrationEffect}.
+ *
+ * <p>Vibration effects are represented as a sequence of segments that describes how vibration
+ * amplitude and frequency changes over time. Segments can be described as one of the following:
+ *
+ * <ol>
+ * <li>A predefined vibration effect;
+ * <li>A composable effect primitive;
+ * <li>Fixed amplitude value to be held for a specified duration;
+ * </ol>
+ *
+ * @hide
+ */
+@TestApi
+@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
+public abstract class VibrationEffectSegment implements Parcelable {
+ static final int PARCEL_TOKEN_PREBAKED = 1;
+ static final int PARCEL_TOKEN_PRIMITIVE = 2;
+ static final int PARCEL_TOKEN_STEP = 3;
+
+ /** Prevent subclassing from outside of this package */
+ VibrationEffectSegment() {
+ }
+
+ /**
+ * Gets the estimated duration of the segment in milliseconds.
+ *
+ * <p>For segments with an unknown duration (e.g. prebaked or primitive effects where the length
+ * is device and potentially run-time dependent), this returns -1.
+ */
+ public abstract long getDuration();
+
+ /** Returns true if this segment plays at a non-zero amplitude at some point. */
+ public abstract boolean hasNonZeroAmplitude();
+
+ /** Validates the segment, throwing exceptions if any parameter is invalid. */
+ public abstract void validate();
+
+ /**
+ * Resolves amplitudes set to {@link VibrationEffect#DEFAULT_AMPLITUDE}.
+ *
+ * <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger
+ * than {@link VibrationEffect#MAX_AMPLITUDE}.
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude);
+
+ /**
+ * Scale the segment intensity with the given factor.
+ *
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
+
+ /**
+ * Applies given effect strength to prebaked effects.
+ *
+ * @param effectStrength new effect strength to be applied, one of
+ * VibrationEffect.EFFECT_STRENGTH_*.
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
+
+ @NonNull
+ public static final Creator<VibrationEffectSegment> CREATOR =
+ new Creator<VibrationEffectSegment>() {
+ @Override
+ public VibrationEffectSegment createFromParcel(Parcel in) {
+ switch (in.readInt()) {
+ case PARCEL_TOKEN_STEP:
+ return new StepSegment(in);
+ case PARCEL_TOKEN_PREBAKED:
+ return new PrebakedSegment(in);
+ case PARCEL_TOKEN_PRIMITIVE:
+ return new PrimitiveSegment(in);
+ default:
+ throw new IllegalStateException(
+ "Unexpected vibration event type token in parcel.");
+ }
+ }
+
+ @Override
+ public VibrationEffectSegment[] newArray(int size) {
+ return new VibrationEffectSegment[size];
+ }
+ };
+}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index e68f330..7e40497 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -358,6 +358,38 @@
public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
/**
+ * Namespace for all statsd java features that can be applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
+
+ /**
+ * Namespace for all statsd java features that are applied on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
+
+ /**
+ * Namespace for all statsd native features that can be applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
+
+ /**
+ * Namespace for all statsd native features that are applied on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
+
+ /**
* Namespace for storage-related features.
*
* @deprecated Replace storage namespace with storage_native_boot.
@@ -456,7 +488,8 @@
*/
@NonNull
private static final List<String> PUBLIC_NAMESPACES =
- Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME);
+ Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
+ NAMESPACE_STATSD_JAVA_BOOT);
/**
* Privacy related properties definitions.
*
@@ -505,38 +538,6 @@
"connectivity_thermal_power_manager";
/**
- * Namespace for all statsd java features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
-
- /**
- * Namespace for all statsd java features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
-
- /**
- * Namespace for all statsd native features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
-
- /**
- * Namespace for all statsd native features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
-
- /**
* Namespace for configuration related features.
*
* @hide
diff --git a/core/java/android/service/notification/ScheduleCalendar.java b/core/java/android/service/notification/ScheduleCalendar.java
index 6ed966e..314c97d 100644
--- a/core/java/android/service/notification/ScheduleCalendar.java
+++ b/core/java/android/service/notification/ScheduleCalendar.java
@@ -57,25 +57,18 @@
}
/**
- * Sets next alarm of the schedule if the saved next alarm has passed or is further
- * in the future than given nextAlarm
+ * Sets next alarm of the schedule
* @param now current time in milliseconds
* @param nextAlarm time of next alarm in milliseconds
*/
public void maybeSetNextAlarm(long now, long nextAlarm) {
if (mSchedule != null && mSchedule.exitAtAlarm) {
- // alarm canceled
if (nextAlarm == 0) {
+ // alarm canceled
mSchedule.nextAlarm = 0;
- }
- // only allow alarms in the future
- if (nextAlarm > now) {
- if (mSchedule.nextAlarm == 0 || mSchedule.nextAlarm < now) {
- mSchedule.nextAlarm = nextAlarm;
- } else {
- // store earliest alarm
- mSchedule.nextAlarm = Math.min(mSchedule.nextAlarm, nextAlarm);
- }
+ } else if (nextAlarm > now) {
+ // only allow alarms in the future
+ mSchedule.nextAlarm = nextAlarm;
} else if (mSchedule.nextAlarm < now) {
if (DEBUG) {
Log.d(TAG, "All alarms are in the past " + mSchedule.nextAlarm);
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
index 8d798c3..297f00a4 100644
--- a/core/java/android/service/translation/ITranslationService.aidl
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -16,7 +16,8 @@
package android.service.translation;
-import android.view.translation.TranslationSpec;
+import android.os.ResultReceiver;
+import android.view.translation.TranslationContext;
import com.android.internal.os.IResultReceiver;
/**
@@ -31,6 +32,9 @@
oneway interface ITranslationService {
void onConnected();
void onDisconnected();
- void onCreateTranslationSession(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
- int sessionId, in IResultReceiver receiver);
+ void onCreateTranslationSession(in TranslationContext translationContext, int sessionId,
+ in IResultReceiver receiver);
+
+ void onTranslationCapabilitiesRequest(int sourceFormat, int targetFormat,
+ in ResultReceiver receiver);
}
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
index c5f1f04..7edf2e2 100644
--- a/core/java/android/service/translation/TranslationService.java
+++ b/core/java/android/service/translation/TranslationService.java
@@ -34,8 +34,12 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.ArraySet;
import android.util.Log;
import android.view.translation.ITranslationDirectManager;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationManager;
import android.view.translation.TranslationRequest;
import android.view.translation.TranslationResponse;
@@ -43,6 +47,9 @@
import com.android.internal.os.IResultReceiver;
+import java.util.Set;
+import java.util.function.Consumer;
+
/**
* Service for translating text.
* @hide
@@ -92,10 +99,20 @@
}
@Override
- public void onCreateTranslationSession(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ public void onCreateTranslationSession(TranslationContext translationContext,
int sessionId, IResultReceiver receiver) throws RemoteException {
mHandler.sendMessage(obtainMessage(TranslationService::handleOnCreateTranslationSession,
- TranslationService.this, sourceSpec, destSpec, sessionId, receiver));
+ TranslationService.this, translationContext, sessionId, receiver));
+ }
+
+ @Override
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ mHandler.sendMessage(
+ obtainMessage(TranslationService::handleOnTranslationCapabilitiesRequest,
+ TranslationService.this, sourceFormat, targetFormat,
+ resultReceiver));
}
};
@@ -194,14 +211,13 @@
/**
* TODO: fill in javadoc.
*
- * @param sourceSpec
- * @param destSpec
+ * @param translationContext
* @param sessionId
*/
// TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
// solution to make it's safe.
- public abstract void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId);
+ public abstract void onCreateTranslationSession(@NonNull TranslationContext translationContext,
+ int sessionId);
/**
* TODO: fill in javadoc.
@@ -222,12 +238,27 @@
@NonNull CancellationSignal cancellationSignal,
@NonNull OnTranslationResultCallback callback);
+ /**
+ * TODO: fill in javadoc
+ * TODO: make this abstract again once aiai is ready.
+ *
+ * <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p>
+ *
+ * @param sourceFormat
+ * @param targetFormat
+ * @param callback
+ */
+ public abstract void onTranslationCapabilitiesRequest(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull Consumer<Set<TranslationCapability>> callback);
+
// TODO(b/176464808): Need to handle client dying case
- // TODO(b/176464808): Need to handle the failure case. e.g. if the specs does not support
+ // TODO(b/176464808): Need to handle the failure case. e.g. if the context is not supported.
- private void handleOnCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ private void handleOnCreateTranslationSession(@NonNull TranslationContext translationContext,
+ int sessionId, IResultReceiver resultReceiver) {
try {
final Bundle extras = new Bundle();
extras.putBinder(EXTRA_SERVICE_BINDER, mClientInterface.asBinder());
@@ -236,6 +267,24 @@
} catch (RemoteException e) {
Log.w(TAG, "RemoteException sending client interface: " + e);
}
- onCreateTranslationSession(sourceSpec, destSpec, sessionId);
+ onCreateTranslationSession(translationContext, sessionId);
+ }
+
+ private void handleOnTranslationCapabilitiesRequest(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ onTranslationCapabilitiesRequest(sourceFormat, targetFormat,
+ new Consumer<Set<TranslationCapability>>() {
+ @Override
+ public void accept(Set<TranslationCapability> values) {
+ final ArraySet<TranslationCapability> capabilities = new ArraySet<>(values);
+ final Bundle bundle = new Bundle();
+ bundle.putParcelableArray(TranslationManager.EXTRA_CAPABILITIES,
+ capabilities.toArray(new TranslationCapability[0]));
+ resultReceiver.send(TranslationManager.STATUS_SYNC_CALL_SUCCESS, bundle);
+ }
+ });
+
}
}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index d3eaeae..2a43222 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -26,6 +26,10 @@
import java.util.Locale;
/**
+ * API for sending log output to the {@link Log#LOG_ID_SYSTEM} buffer.
+ *
+ * <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
+ *
* @hide
*/
public final class Slog {
@@ -51,6 +55,12 @@
/**
* Logs a {@link Log.VERBOSE} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void v(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.VERBOSE)) return;
@@ -71,6 +81,12 @@
/**
* Logs a {@link Log.DEBUG} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void d(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.DEBUG)) return;
@@ -90,6 +106,12 @@
/**
* Logs a {@link Log.INFO} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void i(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.INFO)) return;
@@ -114,6 +136,12 @@
/**
* Logs a {@link Log.WARN} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void w(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -123,6 +151,12 @@
/**
* Logs a {@link Log.WARN} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -143,6 +177,12 @@
/**
* Logs a {@link Log.ERROR} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void e(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.ERROR)) return;
@@ -152,6 +192,12 @@
/**
* Logs a {@link Log.ERROR} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.ERROR)) return;
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
new file mode 100644
index 0000000..dc93a47
--- /dev/null
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -0,0 +1,166 @@
+/*
+ * 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.util;
+
+/**
+ * SparseDoubleArrays map integers to doubles. Unlike a normal array of doubles,
+ * there can be gaps in the indices. It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Doubles, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys. The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items. It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ *
+ * @see SparseLongArray
+ *
+ * @hide
+ */
+public class SparseDoubleArray implements Cloneable {
+ /**
+ * The int->double map, but storing the doubles as longs using
+ * {@link Double#doubleToRawLongBits(double)}.
+ */
+ private SparseLongArray mValues;
+
+ /** Creates a new SparseDoubleArray containing no mappings. */
+ public SparseDoubleArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseDoubleArray, containing no mappings, that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
+ */
+ public SparseDoubleArray(int initialCapacity) {
+ mValues = new SparseLongArray(initialCapacity);
+ }
+
+ @Override
+ public SparseDoubleArray clone() {
+ SparseDoubleArray clone = null;
+ try {
+ clone = (SparseDoubleArray) super.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse) {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Gets the double mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public double get(int key) {
+ final int index = mValues.indexOfKey(key);
+ if (index < 0) {
+ return 0.0d;
+ }
+ return valueAt(index);
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(int key, double value) {
+ mValues.put(key, Double.doubleToRawLongBits(value));
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * <b>adding</b> its value to the previous mapping from the specified key if there
+ * was one.
+ *
+ * <p>This differs from {@link #put} because instead of replacing any previous value, it adds
+ * (in the numerical sense) to it.
+ */
+ public void add(int key, double summand) {
+ final double oldValue = get(key);
+ put(key, oldValue + summand);
+ }
+
+ /** Returns the number of key-value mappings that this SparseDoubleArray currently stores. */
+ public int size() {
+ return mValues.size();
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseDoubleArray stores.
+ *
+ * @see SparseLongArray#keyAt(int)
+ */
+ public int keyAt(int index) {
+ return mValues.keyAt(index);
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseDoubleArray stores.
+ *
+ * @see SparseLongArray#valueAt(int)
+ */
+ public double valueAt(int index) {
+ return Double.longBitsToDouble(mValues.valueAt(index));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation composes a string by iterating over its mappings.
+ */
+ @Override
+ public String toString() {
+ if (size() <= 0) {
+ return "{}";
+ }
+
+ StringBuilder buffer = new StringBuilder(size() * 34);
+ buffer.append('{');
+ for (int i = 0; i < size(); i++) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ int key = keyAt(i);
+ buffer.append(key);
+ buffer.append('=');
+ double value = valueAt(i);
+ buffer.append(value);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index b28cfb8..2fcaec9 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -130,6 +130,16 @@
public abstract void triggerManagerServiceDump(String where);
/**
+ * Being called while taking a bugreport so that tracing files can be included in the bugreport
+ * when the IME tracing is running. Does nothing otherwise.
+ *
+ * @param pw Print writer
+ */
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ // does nothing by default.
+ }
+
+ /**
* Sets whether ime tracing is enabled.
*
* @param enabled Tells whether ime tracing should be enabled or disabled.
@@ -153,11 +163,6 @@
}
/**
- * Writes the current tracing data to the specific output proto file.
- */
- public abstract void writeTracesToFiles();
-
- /**
* Starts a new IME trace if one is not already started.
*
* @param pw Print writer
@@ -171,14 +176,6 @@
*/
public abstract void stopTrace(@Nullable PrintWriter pw);
- /**
- * Stops the IME trace if one was previously started.
- *
- * @param pw Print writer
- * @param writeToFile If the current buffer should be written to disk or not
- */
- public abstract void stopTrace(@Nullable PrintWriter pw, boolean writeToFile);
-
private static boolean isSystemProcess() {
return ActivityThread.isSystem();
}
diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java
index 35a81b7..17cdc46 100644
--- a/core/java/android/util/imetracing/ImeTracingClientImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java
@@ -99,18 +99,10 @@
}
@Override
- public void writeTracesToFiles() {
- }
-
- @Override
public void startTrace(PrintWriter pw) {
}
@Override
public void stopTrace(PrintWriter pw) {
}
-
- @Override
- public void stopTrace(PrintWriter pw, boolean writeToFile) {
- }
}
diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java
index 77f017a..06e4c50 100644
--- a/core/java/android/util/imetracing/ImeTracingServerImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java
@@ -139,14 +139,6 @@
}
}
- @GuardedBy("mEnabledLock")
- @Override
- public void writeTracesToFiles() {
- synchronized (mEnabledLock) {
- writeTracesToFilesLocked();
- }
- }
-
private void writeTracesToFilesLocked() {
try {
ProtoOutputStream clientsProto = new ProtoOutputStream();
@@ -192,12 +184,6 @@
@Override
public void stopTrace(@Nullable PrintWriter pw) {
- stopTrace(pw, true /* writeToFile */);
- }
-
- @GuardedBy("mEnabledLock")
- @Override
- public void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) {
if (IS_USER) {
Log.w(TAG, "Warn: Tracing is not supported on user builds.");
return;
@@ -213,9 +199,35 @@
+ TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
+ TRACE_FILENAME_IMMS);
sEnabled = false;
- if (writeToFile) {
- writeTracesToFilesLocked();
+ writeTracesToFilesLocked();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ if (IS_USER) {
+ return;
+ }
+ synchronized (mEnabledLock) {
+ if (!isAvailable() || !isEnabled()) {
+ return;
}
+ // Temporarily stop accepting logs from trace event providers. There is a small chance
+ // that we may drop some trace events while writing the file, but we currently need to
+ // live with that. Note that addToBuffer() also has a bug that it doesn't do
+ // read-acquire so flipping sEnabled here doesn't even guarantee that addToBuffer() will
+ // temporarily stop accepting incoming events...
+ // TODO(b/175761228): Implement atomic snapshot to avoid downtime.
+ // TODO(b/175761228): Fix synchronization around sEnabled.
+ sEnabled = false;
+ logAndPrintln(pw, "Writing traces in " + TRACE_DIRNAME + ": "
+ + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
+ + TRACE_FILENAME_IMMS);
+ writeTracesToFilesLocked();
+ sEnabled = true;
}
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index f4d5a7b..98b7dbf 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -50,10 +50,6 @@
super(ITYPE_IME, state, transactionSupplier, controller);
}
- public void applyImeVisibility(boolean setVisible) {
- mController.applyImeVisibility(setVisible);
- }
-
@Override
public void onWindowFocusGained() {
super.onWindowFocusGained();
diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
index c159a12..7fac6c5 100644
--- a/core/java/android/view/InputEventAssigner.java
+++ b/core/java/android/view/InputEventAssigner.java
@@ -45,13 +45,13 @@
public class InputEventAssigner {
private static final String TAG = "InputEventAssigner";
private boolean mHasUnprocessedDown = false;
- private int mEventId = INVALID_INPUT_EVENT_ID;
+ private int mDownEventId = INVALID_INPUT_EVENT_ID;
/**
- * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset
- * the 'down' state to assign the latest input event to the current frame.
+ * Notify InputEventAssigner that a frame has been processed. We no longer need to keep track of
+ * the DOWN event because a frame has already been produced for it.
*/
- public void onChoreographerCallback() {
+ public void notifyFrameProcessed() {
// Mark completion of this frame. Use newest input event from now on.
mHasUnprocessedDown = false;
}
@@ -62,31 +62,22 @@
* @return the id of the input event to use for the current frame
*/
public int processEvent(InputEvent event) {
- if (event instanceof KeyEvent) {
- // We will not do any special handling for key events
- return event.getId();
- }
-
if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
- final int action = motionEvent.getActionMasked();
-
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- mHasUnprocessedDown = false;
+ if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) {
+ final int action = motionEvent.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mHasUnprocessedDown = true;
+ mDownEventId = event.getId();
+ }
+ if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) {
+ return mDownEventId;
+ }
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mHasUnprocessedDown = false;
+ }
}
- if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) {
- mHasUnprocessedDown = true;
- mEventId = event.getId();
- // This will remain 'true' even if we receive a MOVE event, as long as choreographer
- // hasn't invoked the 'CALLBACK_INPUT' callback.
- }
- // Don't update the event id if we haven't processed DOWN yet.
- if (!mHasUnprocessedDown) {
- mEventId = event.getId();
- }
- return mEventId;
}
-
- throw new IllegalArgumentException("Received unexpected " + event);
+ return event.getId();
}
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7a61df8..a67ec10 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -269,7 +269,7 @@
}
mShownOnFinish = shown;
mFinished = true;
- setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */,
+ setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, mPendingAlpha, 1f /* fraction */,
true /* allowWhenFinished */);
if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8bf78db3..a68f528 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -189,16 +189,34 @@
}
private static final String TAG = "InsetsController";
- private static final int ANIMATION_DURATION_SHOW_MS = 275;
- private static final int ANIMATION_DURATION_HIDE_MS = 340;
+ private static final int ANIMATION_DURATION_MOVE_IN_MS = 275;
+ private static final int ANIMATION_DURATION_MOVE_OUT_MS = 340;
+ private static final int ANIMATION_DURATION_FADE_IN_MS = 500;
+ private static final int ANIMATION_DURATION_FADE_OUT_MS = 1500;
+
+ private static final int ANIMATION_DELAY_DIM_MS = 500;
private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
- public static final Interpolator SYSTEM_BARS_INTERPOLATOR =
+ private static final Interpolator SYSTEM_BARS_INSETS_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ private static final Interpolator SYSTEM_BARS_ALPHA_INTERPOLATOR =
+ new PathInterpolator(0.3f, 0f, 1f, 1f);
+ private static final Interpolator SYSTEM_BARS_DIM_INTERPOLATOR = alphaFraction -> {
+ // While playing dim animation, alphaFraction is changed from 1f to 0f. Here changes it to
+ // time-based fraction for computing delay and interpolation.
+ float fraction = 1 - alphaFraction;
+ final float fractionDelay = (float) ANIMATION_DELAY_DIM_MS / ANIMATION_DURATION_FADE_OUT_MS;
+ if (fraction <= fractionDelay) {
+ return 1f;
+ } else {
+ float innerFraction = (fraction - fractionDelay) / (1f - fractionDelay);
+ return 1f - SYSTEM_BARS_ALPHA_INTERPOLATOR.getInterpolation(innerFraction);
+ }
+ };
private static final Interpolator SYNC_IME_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 0f, 1f);
private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
@@ -206,6 +224,9 @@
private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 1f, 1f);
+ /** The amount IME will move up/down when animating in floating mode. */
+ private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;
+
static final boolean DEBUG = false;
static final boolean WARN = false;
@@ -278,14 +299,12 @@
public static class InternalAnimationControlListener
implements WindowInsetsAnimationControlListener {
- /** The amount IME will move up/down when animating in floating mode. */
- protected static final int FLOATING_IME_BOTTOM_INSET = -80;
-
private WindowInsetsAnimationController mController;
private ValueAnimator mAnimator;
private final boolean mShow;
private final boolean mHasAnimationCallbacks;
private final @InsetsType int mRequestedTypes;
+ private final @Behavior int mBehavior;
private final long mDurationMs;
private final boolean mDisable;
private final int mFloatingImeBottomInset;
@@ -301,10 +320,12 @@
};
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
- int requestedTypes, boolean disable, int floatingImeBottomInset) {
+ @InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
+ int floatingImeBottomInset) {
mShow = show;
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
+ mBehavior = behavior;
mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
@@ -335,7 +356,7 @@
Insets end = mShow
? controller.getShownStateInsets()
: hiddenInsets;
- Interpolator insetsInterpolator = getInterpolator();
+ Interpolator insetsInterpolator = getInsetsInterpolator();
Interpolator alphaInterpolator = getAlphaInterpolator();
mAnimator.addUpdateListener(animation -> {
float rawFraction = animation.getAnimatedFraction();
@@ -379,7 +400,7 @@
+ mRequestedTypes);
}
- Interpolator getInterpolator() {
+ protected Interpolator getInsetsInterpolator() {
if ((mRequestedTypes & ime()) != 0) {
if (mHasAnimationCallbacks) {
return SYNC_IME_INTERPOLATOR;
@@ -389,7 +410,12 @@
return FAST_OUT_LINEAR_IN_INTERPOLATOR;
}
} else {
- return SYSTEM_BARS_INTERPOLATOR;
+ if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+ return SYSTEM_BARS_INSETS_INTERPOLATOR;
+ } else {
+ // Makes insets stay at the shown position.
+ return input -> mShow ? 1f : 0f;
+ }
}
}
@@ -405,7 +431,15 @@
return FAST_OUT_LINEAR_IN_INTERPOLATOR;
}
} else {
- return input -> 1f;
+ if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+ return input -> 1f;
+ } else {
+ if (mShow) {
+ return SYSTEM_BARS_ALPHA_INTERPOLATOR;
+ } else {
+ return SYSTEM_BARS_DIM_INTERPOLATOR;
+ }
+ }
}
}
@@ -429,7 +463,11 @@
return ANIMATION_DURATION_UNSYNC_IME_MS;
}
} else {
- return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
+ if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+ return mShow ? ANIMATION_DURATION_MOVE_IN_MS : ANIMATION_DURATION_MOVE_OUT_MS;
+ } else {
+ return mShow ? ANIMATION_DURATION_FADE_IN_MS : ANIMATION_DURATION_FADE_OUT_MS;
+ }
}
}
}
@@ -882,7 +920,8 @@
hide(types, false /* fromIme */);
}
- void hide(@InsetsType int types, boolean fromIme) {
+ @VisibleForTesting
+ public void hide(@InsetsType int types, boolean fromIme) {
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
mHost.getInputMethodManager(), null /* icProto */);
@@ -1274,19 +1313,6 @@
getSourceConsumer(ITYPE_IME).onWindowFocusLost();
}
- /**
- * Used by {@link ImeInsetsSourceConsumer} when IME decides to be shown/hidden.
- * @hide
- */
- @VisibleForTesting
- public void applyImeVisibility(boolean setVisible) {
- if (setVisible) {
- show(Type.IME, true /* fromIme */);
- } else {
- hide(Type.IME);
- }
- }
-
@VisibleForTesting
public @AnimationType int getAnimationType(@InternalInsetsType int type) {
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
@@ -1358,14 +1384,14 @@
boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
final InternalAnimationControlListener listener = new InternalAnimationControlListener(
- show, hasAnimationCallbacks, types, skipAnim || mAnimationsDisabled,
- mHost.dipToPx(InternalAnimationControlListener.FLOATING_IME_BOTTOM_INSET));
+ show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
+ skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
// We are about to playing the default animation (show/hide). Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
controlAnimationUnchecked(
types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
- listener.getDurationMs(), listener.getInterpolator(),
+ listener.getDurationMs(), listener.getInsetsInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
!hasAnimationCallbacks /* useInsetsAnimationThread */);
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index 05636de..bd20f5b 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -26,6 +26,9 @@
import com.android.internal.R;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* The top line of content in a notification view.
* This includes the text views and badges but excludes the icon and the expander.
@@ -34,17 +37,23 @@
*/
@RemoteViews.RemoteView
public class NotificationTopLineView extends ViewGroup {
+ private final OverflowAdjuster mOverflowAdjuster = new OverflowAdjuster();
private final int mGravityY;
private final int mChildMinWidth;
+ private final int mChildHideWidth;
@Nullable private View mAppName;
@Nullable private View mTitle;
private View mHeaderText;
+ private View mHeaderTextDivider;
private View mSecondaryHeaderText;
+ private View mSecondaryHeaderTextDivider;
private OnClickListener mFeedbackListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
private View mFeedbackIcon;
private int mHeaderTextMarginEnd;
+ private Set<View> mViewsToDisappear = new HashSet<>();
+
private int mMaxAscent;
private int mMaxDescent;
@@ -66,6 +75,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = getResources();
mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+ mChildHideWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_hide_width);
// NOTE: Implementation only supports TOP, BOTTOM, and CENTER_VERTICAL gravities,
// with CENTER_VERTICAL being the default.
@@ -88,7 +98,9 @@
mAppName = findViewById(R.id.app_name_text);
mTitle = findViewById(R.id.title);
mHeaderText = findViewById(R.id.header_text);
+ mHeaderTextDivider = findViewById(R.id.header_text_divider);
mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
+ mSecondaryHeaderTextDivider = findViewById(R.id.header_text_secondary_divider);
mFeedbackIcon = findViewById(R.id.feedback);
}
@@ -125,48 +137,37 @@
maxChildHeight = Math.max(maxChildHeight, childHeight);
}
+ mViewsToDisappear.clear();
// Ensure that there is at least enough space for the icons
int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd());
if (totalWidth > givenWidth - endMargin) {
int overFlow = totalWidth - givenWidth + endMargin;
- // First shrink the app name, down to a minimum size
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mAppName, mChildMinWidth);
-
- // Next, shrink the header text (this usually has subText)
- // This shrinks the subtext first, but not all the way (yet!)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, mChildMinWidth);
-
- // Next, shrink the secondary header text (this rarely has conversationTitle)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mSecondaryHeaderText, 0);
-
- // Next, shrink the title text (this has contentTitle; only in headerless views)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mTitle, mChildMinWidth);
-
- // Finally, if there is still overflow, shrink the header down to 0 if still necessary.
- shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, 0);
+ mOverflowAdjuster.resetForOverflow(overFlow, heightSpec)
+ // First shrink the app name, down to a minimum size
+ .adjust(mAppName, null, mChildMinWidth)
+ // Next, shrink the header text (this usually has subText)
+ // This shrinks the subtext first, but not all the way (yet!)
+ .adjust(mHeaderText, mHeaderTextDivider, mChildMinWidth)
+ // Next, shrink the secondary header text (this rarely has conversationTitle)
+ .adjust(mSecondaryHeaderText, mSecondaryHeaderTextDivider, 0)
+ // Next, shrink the title text (this has contentTitle; only in headerless views)
+ .adjust(mTitle, null, mChildMinWidth)
+ // Next, shrink the header down to 0 if still necessary.
+ .adjust(mHeaderText, mHeaderTextDivider, 0)
+ // Finally, shrink the title to 0 if necessary (media is super cramped)
+ .adjust(mTitle, null, 0)
+ // Clean up
+ .finish();
}
setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
}
- private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
- int minimumWidth) {
- if (targetView != null) {
- final int oldWidth = targetView.getMeasuredWidth();
- if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
- // we're still too big
- int newSize = Math.max(minimumWidth, oldWidth - overFlow);
- int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
- targetView.measure(childWidthSpec, heightSpec);
- overFlow -= oldWidth - newSize;
- }
- }
- return overFlow;
- }
-
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int left = getPaddingStart();
+ final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ final int width = getWidth();
+ int start = getPaddingStart();
int childCount = getChildCount();
int ownHeight = b - t;
int childSpace = ownHeight - mPaddingTop - mPaddingBottom;
@@ -182,8 +183,6 @@
}
int childHeight = child.getMeasuredHeight();
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
- int layoutLeft;
- int layoutRight;
// Calculate vertical alignment of the views, accounting for the view baselines
int childTop;
@@ -219,19 +218,16 @@
default:
childTop = mPaddingTop;
}
-
- left += params.getMarginStart();
- int right = left + child.getMeasuredWidth();
- layoutLeft = left;
- layoutRight = right;
- left = right + params.getMarginEnd();
-
- if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- int ltrLeft = layoutLeft;
- layoutLeft = getWidth() - layoutRight;
- layoutRight = getWidth() - ltrLeft;
+ if (mViewsToDisappear.contains(child)) {
+ child.layout(start, childTop, start, childTop + childHeight);
+ } else {
+ start += params.getMarginStart();
+ int end = start + child.getMeasuredWidth();
+ int layoutLeft = isRtl ? width - end : start;
+ int layoutRight = isRtl ? width - start : end;
+ start = end + params.getMarginEnd();
+ child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
}
- child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
}
updateTouchListener();
}
@@ -400,4 +396,83 @@
}
return mTouchListener.onTouchUp(upX, upY, downX, downY);
}
+
+ private final class OverflowAdjuster {
+ private int mOverflow;
+ private int mHeightSpec;
+ private View mRegrowView;
+
+ OverflowAdjuster resetForOverflow(int overflow, int heightSpec) {
+ mOverflow = overflow;
+ mHeightSpec = heightSpec;
+ mRegrowView = null;
+ return this;
+ }
+
+ /**
+ * Shrink the targetView's width by up to overFlow, down to minimumWidth.
+ * @param targetView the view to shrink the width of
+ * @param targetDivider a divider view which should be set to 0 width if the targetView is
+ * @param minimumWidth the minimum width allowed for the targetView
+ * @return this object
+ */
+ OverflowAdjuster adjust(View targetView, View targetDivider, int minimumWidth) {
+ if (mOverflow <= 0 || targetView == null || targetView.getVisibility() == View.GONE) {
+ return this;
+ }
+ final int oldWidth = targetView.getMeasuredWidth();
+ if (oldWidth <= minimumWidth) {
+ return this;
+ }
+ // we're too big
+ int newSize = Math.max(minimumWidth, oldWidth - mOverflow);
+ if (minimumWidth == 0 && newSize < mChildHideWidth
+ && mRegrowView != null && mRegrowView != targetView) {
+ // View is so small it's better to hide it entirely (and its divider and margins)
+ // so we can give that space back to another previously shrunken view.
+ newSize = 0;
+ }
+
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ targetView.measure(childWidthSpec, mHeightSpec);
+ mOverflow -= oldWidth - newSize;
+
+ if (newSize == 0) {
+ mViewsToDisappear.add(targetView);
+ mOverflow -= getHorizontalMargins(targetView);
+ if (targetDivider != null && targetDivider.getVisibility() != GONE) {
+ mViewsToDisappear.add(targetDivider);
+ int oldDividerWidth = targetDivider.getMeasuredWidth();
+ int dividerWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.AT_MOST);
+ targetDivider.measure(dividerWidthSpec, mHeightSpec);
+ mOverflow -= (oldDividerWidth + getHorizontalMargins(targetDivider));
+ }
+ }
+ if (mOverflow < 0 && mRegrowView != null) {
+ // We're now under-flowing, so regrow the last view.
+ final int regrowCurrentSize = mRegrowView.getMeasuredWidth();
+ final int maxSize = regrowCurrentSize - mOverflow;
+ int regrowWidthSpec = MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST);
+ mRegrowView.measure(regrowWidthSpec, mHeightSpec);
+ finish();
+ return this;
+ }
+
+ if (newSize != 0) {
+ // if we shrunk this view (but did not completely hide it) store it for potential
+ // re-growth if we proactively shorten a future view.
+ mRegrowView = targetView;
+ }
+ return this;
+ }
+
+ void finish() {
+ resetForOverflow(0, 0);
+ }
+
+ private int getHorizontalMargins(View view) {
+ MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
+ return params.getMarginStart() + params.getMarginEnd();
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 3ffe0c6..f1eef9f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1296,7 +1296,7 @@
mBlastBufferQueue.destroy();
}
mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight, mFormat, true /* TODO */);
+ mSurfaceHeight, mFormat);
}
private void onDrawFinished() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4fb611..dbccf10 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -330,7 +330,6 @@
private boolean mUseBLASTAdapter;
private boolean mForceDisableBLAST;
- private boolean mEnableTripleBuffering;
private boolean mFastScrollSoundEffectsEnabled;
@@ -474,6 +473,7 @@
FrameInfo frameInfo = mChoreographer.mFrameInfo;
mViewFrameInfo.populateFrameInfo(frameInfo);
mViewFrameInfo.reset();
+ mInputEventAssigner.notifyFrameProcessed();
return frameInfo;
}
@@ -1179,9 +1179,6 @@
if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
mUseBLASTAdapter = true;
}
- if ((res & WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING) != 0) {
- mEnableTripleBuffering = true;
- }
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
@@ -1373,17 +1370,10 @@
// can be used by code on the system process to escape that and enable
// HW accelerated drawing. (This is basically for the lock screen.)
- final boolean fakeHwAccelerated = (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
final boolean forceHwAccelerated = (attrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
- if (fakeHwAccelerated) {
- // This is exclusively for the preview windows the window manager
- // shows for launching applications, so they will look more like
- // the app being launched.
- mAttachInfo.mHardwareAccelerationRequested = true;
- } else if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
+ if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.destroy();
}
@@ -1907,7 +1897,7 @@
Surface ret = null;
if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, width, height,
- format, mEnableTripleBuffering);
+ format);
// We only return the Surface the first time, as otherwise
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
@@ -8502,11 +8492,6 @@
consumedBatches = false;
}
doProcessInputEvents();
- if (consumedBatches) {
- // Must be done after we processed the input events, to mark the completion of the frame
- // from the input point of view
- mInputEventAssigner.onChoreographerCallback();
- }
return consumedBatches;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 04512c9..9df87dc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2218,26 +2218,6 @@
public int flags;
/**
- * If the window has requested hardware acceleration, but this is not
- * allowed in the process it is in, then still render it as if it is
- * hardware accelerated. This is used for the starting preview windows
- * in the system process, which don't need to have the overhead of
- * hardware acceleration (they are just a static rendering), but should
- * be rendered as such to match the actual window of the app even if it
- * is hardware accelerated.
- * Even if the window isn't hardware accelerated, still do its rendering
- * as if it was.
- * Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows
- * that need hardware acceleration (e.g. LockScreen), where hardware acceleration
- * is generally disabled. This flag must be specified in addition to
- * {@link #FLAG_HARDWARE_ACCELERATED} to enable hardware acceleration for system
- * windows.
- *
- * @hide
- */
- public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
-
- /**
* In the system process, we globally do not use hardware acceleration
* because there are many threads doing UI there and they conflict.
* If certain parts of the UI that really do want to use hardware
@@ -2463,7 +2443,6 @@
* @hide
*/
@IntDef(flag = true, prefix="PRIVATE_FLAG_", value = {
- PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
@@ -2499,10 +2478,6 @@
@UnsupportedAppUsage
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
- equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
- name = "FAKE_HARDWARE_ACCELERATED"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
name = "FORCE_HARDWARE_ACCELERATED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 47ac1ee..18013e8 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -118,7 +118,6 @@
public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
public static final int ADD_FLAG_APP_VISIBLE = 0x2;
- public static final int ADD_FLAG_USE_TRIPLE_BUFFERING = 0x4;
public static final int ADD_FLAG_USE_BLAST = 0x8;
/**
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 37220fe..c5bce28 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -608,7 +608,12 @@
Preconditions.checkArgumentNonnegative(afterLength);
final Editable content = getEditable();
- if (content == null) return null;
+ // If {@link #getEditable()} is null or {@code mEditable} is equal to {@link #getEditable()}
+ // (a.k.a, a fake editable), it means we cannot get valid content from the editable, so
+ // fallback to retrieve surrounding text from other APIs.
+ if (content == null || mEditable == content) {
+ return InputConnection.super.getSurroundingText(beforeLength, afterLength, flags);
+ }
int selStart = Selection.getSelectionStart(content);
int selEnd = Selection.getSelectionEnd(content);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 34a60bb..38019c9 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -325,16 +325,16 @@
CharSequence textBeforeCursor = getTextBeforeCursor(beforeLength, flags);
if (textBeforeCursor == null) {
- textBeforeCursor = "";
+ return null;
+ }
+ CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
+ if (textAfterCursor == null) {
+ return null;
}
CharSequence selectedText = getSelectedText(flags);
if (selectedText == null) {
selectedText = "";
}
- CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
- if (textAfterCursor == null) {
- textAfterCursor = "";
- }
CharSequence surroundingText =
TextUtils.concat(textBeforeCursor, selectedText, textAfterCursor);
return new SurroundingText(surroundingText, textBeforeCursor.length(),
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9872dc0..3cd3902 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -511,7 +511,6 @@
static final int MSG_TIMEOUT_INPUT_EVENT = 6;
static final int MSG_FLUSH_INPUT_EVENT = 7;
static final int MSG_REPORT_FULLSCREEN_MODE = 10;
- static final int MSG_APPLY_IME_VISIBILITY = 20;
static final int MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX = 30;
private static boolean isAutofillUIShowing(View servedView) {
@@ -985,17 +984,6 @@
}
return;
}
- case MSG_APPLY_IME_VISIBILITY: {
- synchronized (mH) {
- if (mImeInsetsConsumer != null) {
- ImeTracing.getInstance().triggerClientDump(
- "ImeInsetsSourceConsumer#applyImeVisibility",
- InputMethodManager.this, null /* icProto */);
- mImeInsetsConsumer.applyImeVisibility(msg.arg1 != 0);
- }
- }
- return;
- }
case MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX: {
final float[] matrixValues = (float[]) msg.obj;
final int bindSequence = msg.arg1;
@@ -1090,12 +1078,6 @@
}
@Override
- public void applyImeVisibility(boolean setVisible) {
- mH.obtainMessage(MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, 0)
- .sendToTarget();
- }
-
- @Override
public void updateActivityViewToScreenMatrix(int bindSequence, float[] matrixValues) {
mH.obtainMessage(MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX, bindSequence, 0,
matrixValues).sendToTarget();
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index d347f31..dbc32e9 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -18,7 +18,9 @@
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.ResultReceiver;
import android.view.autofill.AutofillId;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import com.android.internal.os.IResultReceiver;
@@ -30,17 +32,17 @@
* {@hide}
*/
oneway interface ITranslationManager {
- void getSupportedLocales(in IResultReceiver receiver, int userId);
- void onSessionCreated(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+ void onTranslationCapabilitiesRequest(int sourceFormat, int destFormat,
+ in ResultReceiver receiver, int userId);
+ void onSessionCreated(in TranslationContext translationContext,
int sessionId, in IResultReceiver receiver, int userId);
void updateUiTranslationState(int state, in TranslationSpec sourceSpec,
- in TranslationSpec destSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
+ in TranslationSpec targetSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
int userId);
// deprecated
void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec,
- in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId,
- int userId);
+ in TranslationSpec targetSpec, in List<AutofillId> viewIds, int taskId, int userId);
void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationCapability.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationCapability.aidl
index cab5ad2..3a80b17 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationCapability.aidl
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package android.view.translation;
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+parcelable TranslationCapability;
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
new file mode 100644
index 0000000..28b2113
--- /dev/null
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -0,0 +1,289 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Capability class holding information for a pair of {@link TranslationSpec}s.
+ *
+ * <p>Holds information and limitations on how to create a {@link TranslationContext} which can
+ * be used by {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genConstructor = false)
+public final class TranslationCapability implements Parcelable {
+
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_DOWNLOADING = 2;
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_ON_DEVICE = 3;
+
+ /**
+ * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+ */
+ private final @ModelState int mState;
+
+ /**
+ * {@link TranslationSpec} describing the source data specs for this
+ * capability.
+ */
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+
+ /**
+ * {@link TranslationSpec} describing the target data specs for this
+ * capability.
+ */
+ @NonNull
+ private final TranslationSpec mTargetSpec;
+
+ /**
+ * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+ *
+ * <p>Translation service will still support translation requests for this capability.</p>
+ */
+ private final boolean mUiTranslationEnabled;
+
+ /**
+ * Translation flags for settings that are supported by the
+ * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+ * provided in this capability.
+ */
+ private final @TranslationContext.TranslationFlag int mSupportedTranslationFlags;
+
+ /**
+ * Constructor for creating a {@link TranslationCapability}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public TranslationCapability(@ModelState int state, @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec, boolean uiTranslationEnabled,
+ @TranslationContext.TranslationFlag int supportedTranslationFlags) {
+ Objects.requireNonNull(sourceSpec, "sourceSpec should not be null");
+ Objects.requireNonNull(targetSpec, "targetSpec should not be null");
+
+ this.mState = state;
+ this.mSourceSpec = sourceSpec;
+ this.mTargetSpec = targetSpec;
+ this.mUiTranslationEnabled = uiTranslationEnabled;
+ this.mSupportedTranslationFlags = supportedTranslationFlags;
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationCapability.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "STATE_", value = {
+ STATE_AVAILABLE_TO_DOWNLOAD,
+ STATE_DOWNLOADING,
+ STATE_ON_DEVICE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface ModelState {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String modelStateToString(@ModelState int value) {
+ switch (value) {
+ case STATE_AVAILABLE_TO_DOWNLOAD:
+ return "STATE_AVAILABLE_TO_DOWNLOAD";
+ case STATE_DOWNLOADING:
+ return "STATE_DOWNLOADING";
+ case STATE_ON_DEVICE:
+ return "STATE_ON_DEVICE";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+ */
+ @DataClass.Generated.Member
+ public @ModelState int getState() {
+ return mState;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the source data specs for this
+ * capability.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getSourceSpec() {
+ return mSourceSpec;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the target data specs for this
+ * capability.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getTargetSpec() {
+ return mTargetSpec;
+ }
+
+ /**
+ * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+ *
+ * <p>Translation service will still support translation requests for this capability.</p>
+ */
+ @DataClass.Generated.Member
+ public boolean isUiTranslationEnabled() {
+ return mUiTranslationEnabled;
+ }
+
+ /**
+ * Translation flags for settings that are supported by the
+ * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+ * provided in this capability.
+ */
+ @DataClass.Generated.Member
+ public @TranslationContext.TranslationFlag int getSupportedTranslationFlags() {
+ return mSupportedTranslationFlags;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationCapability { " +
+ "state = " + modelStateToString(mState) + ", " +
+ "sourceSpec = " + mSourceSpec + ", " +
+ "targetSpec = " + mTargetSpec + ", " +
+ "uiTranslationEnabled = " + mUiTranslationEnabled + ", " +
+ "supportedTranslationFlags = " + mSupportedTranslationFlags +
+ " }";
+ }
+
+ @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 (mUiTranslationEnabled) flg |= 0x8;
+ dest.writeByte(flg);
+ dest.writeInt(mState);
+ dest.writeTypedObject(mSourceSpec, flags);
+ dest.writeTypedObject(mTargetSpec, flags);
+ dest.writeInt(mSupportedTranslationFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationCapability(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean uiTranslationEnabled = (flg & 0x8) != 0;
+ int state = in.readInt();
+ TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ int supportedTranslationFlags = in.readInt();
+
+ this.mState = state;
+
+ if (!(mState == STATE_AVAILABLE_TO_DOWNLOAD)
+ && !(mState == STATE_DOWNLOADING)
+ && !(mState == STATE_ON_DEVICE)) {
+ throw new java.lang.IllegalArgumentException(
+ "state was " + mState + " but must be one of: "
+ + "STATE_AVAILABLE_TO_DOWNLOAD(" + STATE_AVAILABLE_TO_DOWNLOAD + "), "
+ + "STATE_DOWNLOADING(" + STATE_DOWNLOADING + "), "
+ + "STATE_ON_DEVICE(" + STATE_ON_DEVICE + ")");
+ }
+
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mUiTranslationEnabled = uiTranslationEnabled;
+ this.mSupportedTranslationFlags = supportedTranslationFlags;
+ com.android.internal.util.AnnotationValidations.validate(
+ TranslationContext.TranslationFlag.class, null, mSupportedTranslationFlags);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationCapability> CREATOR
+ = new Parcelable.Creator<TranslationCapability>() {
+ @Override
+ public TranslationCapability[] newArray(int size) {
+ return new TranslationCapability[size];
+ }
+
+ @Override
+ public TranslationCapability createFromParcel(@NonNull android.os.Parcel in) {
+ return new TranslationCapability(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1616438309593L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
+ inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationContext.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationContext.aidl
index cab5ad2..cb6c23f 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationContext.aidl
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package android.view.translation;
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+parcelable TranslationContext;
diff --git a/core/java/android/view/translation/TranslationContext.java b/core/java/android/view/translation/TranslationContext.java
new file mode 100644
index 0000000..1d3d182
--- /dev/null
+++ b/core/java/android/view/translation/TranslationContext.java
@@ -0,0 +1,309 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Info class holding information for {@link Translator}s and used by
+ * {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genBuilder = true)
+public final class TranslationContext implements Parcelable {
+
+ /**
+ * This context will perform translations in low latency mode.
+ */
+ public static final @TranslationFlag int FLAG_LOW_LATENCY = 0x1;
+ /**
+ * This context will enable the {@link Translator} to return transliteration results.
+ */
+ public static final @TranslationFlag int FLAG_TRANSLITERATION = 0x2;
+ /**
+ * This context will enable the {@link Translator} to return dictionary results.
+ */
+ public static final @TranslationFlag int FLAG_DICTIONARY_DESCRIPTION = 0x4;
+
+ /**
+ * {@link TranslationSpec} describing the source data to be translated.
+ */
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+
+ /**
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ @NonNull
+ private final TranslationSpec mTargetSpec;
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ private final @TranslationFlag int mTranslationFlags;
+
+ private static int defaultTranslationFlags() {
+ return 0;
+ }
+
+ @DataClass.Suppress({"setSourceSpec", "setTargetSpec"})
+ abstract static class BaseBuilder {
+
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationContext.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(flag = true, prefix = "FLAG_", value = {
+ FLAG_LOW_LATENCY,
+ FLAG_TRANSLITERATION,
+ FLAG_DICTIONARY_DESCRIPTION
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TranslationFlag {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String translationFlagToString(@TranslationFlag int value) {
+ return com.android.internal.util.BitUtils.flagsToString(
+ value, TranslationContext::singleTranslationFlagToString);
+ }
+
+ @DataClass.Generated.Member
+ static String singleTranslationFlagToString(@TranslationFlag int value) {
+ switch (value) {
+ case FLAG_LOW_LATENCY:
+ return "FLAG_LOW_LATENCY";
+ case FLAG_TRANSLITERATION:
+ return "FLAG_TRANSLITERATION";
+ case FLAG_DICTIONARY_DESCRIPTION:
+ return "FLAG_DICTIONARY_DESCRIPTION";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ TranslationContext(
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec,
+ @TranslationFlag int translationFlags) {
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mTranslationFlags = translationFlags;
+
+ com.android.internal.util.Preconditions.checkFlagsArgument(
+ mTranslationFlags,
+ FLAG_LOW_LATENCY
+ | FLAG_TRANSLITERATION
+ | FLAG_DICTIONARY_DESCRIPTION);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * {@link TranslationSpec} describing the source data to be translated.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getSourceSpec() {
+ return mSourceSpec;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getTargetSpec() {
+ return mTargetSpec;
+ }
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ @DataClass.Generated.Member
+ public @TranslationFlag int getTranslationFlags() {
+ return mTranslationFlags;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationContext { " +
+ "sourceSpec = " + mSourceSpec + ", " +
+ "targetSpec = " + mTargetSpec + ", " +
+ "translationFlags = " + translationFlagToString(mTranslationFlags) +
+ " }";
+ }
+
+ @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) { ... }
+
+ dest.writeTypedObject(mSourceSpec, flags);
+ dest.writeTypedObject(mTargetSpec, flags);
+ dest.writeInt(mTranslationFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationContext(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ int translationFlags = in.readInt();
+
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mTranslationFlags = translationFlags;
+
+ com.android.internal.util.Preconditions.checkFlagsArgument(
+ mTranslationFlags,
+ FLAG_LOW_LATENCY
+ | FLAG_TRANSLITERATION
+ | FLAG_DICTIONARY_DESCRIPTION);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationContext> CREATOR
+ = new Parcelable.Creator<TranslationContext>() {
+ @Override
+ public TranslationContext[] newArray(int size) {
+ return new TranslationContext[size];
+ }
+
+ @Override
+ public TranslationContext createFromParcel(@NonNull android.os.Parcel in) {
+ return new TranslationContext(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TranslationContext}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder extends BaseBuilder {
+
+ private @NonNull TranslationSpec mSourceSpec;
+ private @NonNull TranslationSpec mTargetSpec;
+ private @TranslationFlag int mTranslationFlags;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param sourceSpec
+ * {@link TranslationSpec} describing the source data to be translated.
+ * @param targetSpec
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ public Builder(
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec) {
+ mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ }
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTranslationFlags(@TranslationFlag int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTranslationFlags = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TranslationContext build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTranslationFlags = defaultTranslationFlags();
+ }
+ TranslationContext o = new TranslationContext(
+ mSourceSpec,
+ mTargetSpec,
+ mTranslationFlags);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1616199021789L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationContext.java",
+ inputSignatures = "public static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_LOW_LATENCY\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_TRANSLITERATION\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_DICTIONARY_DESCRIPTION\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mTranslationFlags\nprivate static int defaultTranslationFlags()\nclass TranslationContext extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 6554e1a..66b45f3 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -21,24 +21,27 @@
import android.annotation.RequiresFeature;
import android.annotation.SystemService;
import android.annotation.WorkerThread;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
-import android.service.translation.TranslationService;
+import android.os.SynchronousResultReceiver;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.SyncResultReceiver;
+import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
import java.util.Objects;
import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -55,9 +58,9 @@
private static final String TAG = "TranslationManager";
/**
- * Timeout for calls to system_server.
+ * Timeout for calls to system_server, default 1 minute.
*/
- static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+ static final int SYNC_CALLS_TIMEOUT_MS = 60_000;
/**
* The result code from result receiver success.
* @hide
@@ -69,6 +72,17 @@
*/
public static final int STATUS_SYNC_CALL_FAIL = 2;
+ /**
+ * Name of the extra used to pass the translation capabilities.
+ * @hide
+ */
+ public static final String EXTRA_CAPABILITIES = "translation_capabilities";
+
+ // TODO: implement update listeners and propagate updates.
+ @GuardedBy("mLock")
+ private final ArrayMap<Pair<Integer, Integer>, ArrayList<PendingIntent>>
+ mTranslationCapabilityUpdateListeners = new ArrayMap<>();
+
private static final Random ID_GENERATOR = new Random();
private final Object mLock = new Object();
@@ -87,7 +101,7 @@
@NonNull
@GuardedBy("mLock")
- private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Integer> mTranslatorIds =
+ private final ArrayMap<TranslationContext, Integer> mTranslatorIds =
new ArrayMap<>();
@NonNull
@@ -110,23 +124,20 @@
*
* <p><strong>NOTE: </strong>Call on a worker thread.
*
- * @param sourceSpec {@link TranslationSpec} for the data to be translated.
- * @param destSpec {@link TranslationSpec} for the translated data.
+ * @param translationContext {@link TranslationContext} containing the specs for creating the
+ * Translator.
* @return a {@link Translator} to be used for calling translation APIs.
*/
@Nullable
@WorkerThread
- public Translator createTranslator(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec) {
- Objects.requireNonNull(sourceSpec, "sourceSpec cannot be null");
- Objects.requireNonNull(sourceSpec, "destSpec cannot be null");
+ public Translator createTranslator(@NonNull TranslationContext translationContext) {
+ Objects.requireNonNull(translationContext, "translationContext cannot be null");
synchronized (mLock) {
// TODO(b/176464808): Disallow multiple Translator now, it will throw
// IllegalStateException. Need to discuss if we can allow multiple Translators.
- final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
- if (mTranslatorIds.containsKey(specs)) {
- return mTranslators.get(mTranslatorIds.get(specs));
+ if (mTranslatorIds.containsKey(translationContext)) {
+ return mTranslators.get(mTranslatorIds.get(translationContext));
}
int translatorId;
@@ -134,7 +145,7 @@
translatorId = Math.abs(ID_GENERATOR.nextInt());
} while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
- final Translator newTranslator = new Translator(mContext, sourceSpec, destSpec,
+ final Translator newTranslator = new Translator(mContext, translationContext,
translatorId, this, mHandler, mService);
// Start the Translator session and wait for the result
newTranslator.start();
@@ -143,7 +154,7 @@
return null;
}
mTranslators.put(translatorId, newTranslator);
- mTranslatorIds.put(specs, translatorId);
+ mTranslatorIds.put(translationContext, translatorId);
return newTranslator;
} catch (Translator.ServiceBinderReceiver.TimeoutException e) {
// TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor
@@ -155,32 +166,103 @@
}
/**
- * Returns a list of locales supported by the {@link TranslationService}.
+ * Returns a set of {@link TranslationCapability}s describing the capabilities for
+ * {@link Translator}s.
+ *
+ * <p>These translation capabilities contains a source and target {@link TranslationSpec}
+ * representing the data expected for both ends of translation process. The capabilities
+ * provides the information and limitations for generating a {@link TranslationContext}.
+ * The context object can then be used by {@link #createTranslator(TranslationContext)} to
+ * obtain a {@link Translator} for translations.</p>
*
* <p><strong>NOTE: </strong>Call on a worker thread.
*
- * TODO: Change to correct language/locale format
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @return A set of {@link TranslationCapability}s.
*/
@NonNull
@WorkerThread
- public List<String> getSupportedLocales() {
+ public Set<TranslationCapability> getTranslationCapabilities(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat) {
try {
- // TODO: implement it
- final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
- mService.getSupportedLocales(receiver, mContext.getUserId());
- int resutCode = receiver.getIntResult();
- if (resutCode != STATUS_SYNC_CALL_SUCCESS) {
- return Collections.emptyList();
+ final SynchronousResultReceiver receiver = new SynchronousResultReceiver();
+ mService.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, receiver,
+ mContext.getUserId());
+ final SynchronousResultReceiver.Result result =
+ receiver.awaitResult(SYNC_CALLS_TIMEOUT_MS);
+ if (result.resultCode != STATUS_SYNC_CALL_SUCCESS) {
+ return Collections.emptySet();
}
- return receiver.getParcelableResult();
+ return new ArraySet<>(
+ (TranslationCapability[]) result.bundle.getParcelableArray(EXTRA_CAPABILITIES));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } catch (SyncResultReceiver.TimeoutException e) {
- Log.e(TAG, "Timed out getting supported locales: " + e);
- return Collections.emptyList();
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timed out getting supported translation capabilities: " + e);
+ return Collections.emptySet();
}
}
+ /**
+ * Registers a {@link PendingIntent} to listen for updates on states of
+ * {@link TranslationCapability}s.
+ *
+ * <p>IMPORTANT: the pending intent must be called to start a service, or a broadcast if it is
+ * an explicit intent.</p>
+ *
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @param pendingIntent the pending intent to invoke when updates are received.
+ */
+ public void addTranslationCapabilityUpdateListener(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+ synchronized (mLock) {
+ final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+ mTranslationCapabilityUpdateListeners.computeIfAbsent(formatPair,
+ (formats) -> new ArrayList<>()).add(pendingIntent);
+ }
+ }
+
+ /**
+ * Unregisters a {@link PendingIntent} to listen for updates on states of
+ * {@link TranslationCapability}s.
+ *
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @param pendingIntent the pending intent to unregister
+ */
+ public void removeTranslationCapabilityUpdateListener(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+ synchronized (mLock) {
+ final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+ if (mTranslationCapabilityUpdateListeners.containsKey(formatPair)) {
+ final ArrayList<PendingIntent> intents =
+ mTranslationCapabilityUpdateListeners.get(formatPair);
+ if (intents.contains(pendingIntent)) {
+ intents.remove(pendingIntent);
+ } else {
+ Log.w(TAG, "pending intent=" + pendingIntent + " does not exist in "
+ + "mTranslationCapabilityUpdateListeners");
+ }
+ } else {
+ Log.w(TAG, "format pair=" + formatPair + " does not exist in "
+ + "mTranslationCapabilityUpdateListeners");
+ }
+ }
+ }
+
+ //TODO: Add method to propagate updates to mTCapabilityUpdateListeners
+
void removeTranslator(int id) {
synchronized (mLock) {
mTranslators.remove(id);
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 0cc26e1..6b26e06 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -44,7 +44,7 @@
import java.util.function.Consumer;
/**
- * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
+ * The {@link Translator} for translation, defined by a {@link TranslationContext}.
*/
@SuppressLint("NotCloseable")
public class Translator {
@@ -62,10 +62,7 @@
private final Context mContext;
@NonNull
- private final TranslationSpec mSourceSpec;
-
- @NonNull
- private final TranslationSpec mDestSpec;
+ private final TranslationContext mTranslationContext;
@NonNull
private final TranslationManager mManager;
@@ -164,13 +161,11 @@
* @hide
*/
public Translator(@NonNull Context context,
- @NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId,
+ @NonNull TranslationContext translationContext, int sessionId,
@NonNull TranslationManager translationManager, @NonNull Handler handler,
@Nullable ITranslationManager systemServerBinder) {
mContext = context;
- mSourceSpec = sourceSpec;
- mDestSpec = destSpec;
+ mTranslationContext = translationContext;
mId = sessionId;
mManager = translationManager;
mHandler = handler;
@@ -183,7 +178,7 @@
*/
void start() {
try {
- mSystemServerBinder.onSessionCreated(mSourceSpec, mDestSpec, mId,
+ mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
mServiceBinderReceiver, mContext.getUserId());
} catch (RemoteException e) {
Log.w(TAG, "RemoteException calling startSession(): " + e);
@@ -223,8 +218,7 @@
/** @hide */
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
- pw.print(prefix); pw.print("sourceSpec: "); pw.println(mSourceSpec);
- pw.print(prefix); pw.print("destSpec: "); pw.println(mDestSpec);
+ pw.print(prefix); pw.print("translationContext: "); pw.println(mTranslationContext);
}
/**
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 7f934c0..d79ecca 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -376,15 +376,17 @@
}
private Translator createTranslatorIfNeeded(
- TranslationSpec sourceSpec, TranslationSpec destSpec) {
+ TranslationSpec sourceSpec, TranslationSpec targetSpec) {
final TranslationManager tm = mContext.getSystemService(TranslationManager.class);
if (tm == null) {
Log.e(TAG, "Can not find TranslationManager when trying to create translator.");
return null;
}
- final Translator translator = tm.createTranslator(sourceSpec, destSpec);
+ final TranslationContext translationContext = new TranslationContext(sourceSpec,
+ targetSpec, /* translationFlags= */ 0);
+ final Translator translator = tm.createTranslator(translationContext);
if (translator != null) {
- final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
+ final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, targetSpec);
mTranslators.put(specs, translator);
}
return translator;
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 58dfc0d..34ad659 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -640,6 +640,12 @@
mWidth,
mHeight
);
+ } else {
+ // This is TYPE_STRETCH and drawing into a Canvas that isn't a Recording Canvas,
+ // so no effect can be shown. Just end the effect.
+ mState = STATE_IDLE;
+ mDistance = 0;
+ mVelocity = 0;
}
boolean oneLastFrame = false;
@@ -771,8 +777,9 @@
* considered at rest or false if it is still animating.
*/
private boolean isAtEquilibrium() {
- double displacement = mDistance * mHeight; // in pixels
- return Math.abs(mVelocity) < VELOCITY_THRESHOLD
+ double displacement = mDistance * mHeight * LINEAR_STRETCH_INTENSITY; // in pixels
+ double velocity = mVelocity * LINEAR_STRETCH_INTENSITY;
+ return Math.abs(velocity) < VELOCITY_THRESHOLD
&& Math.abs(displacement) < VALUE_THRESHOLD;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 319e788..cb2bba1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5476,14 +5476,14 @@
/**
* Object allowing the modification of a context to overload the system's dynamic colors.
*
- * Only colors from {@link android.R.color#system_primary_0} to
- * {@link android.R.color#system_neutral_1000} can be overloaded.
+ * Only colors from {@link android.R.color#system_accent1_0} to
+ * {@link android.R.color#system_neutral2_1000} can be overloaded.
* @hide
*/
public static final class ColorResources {
// Set of valid colors resources.
- private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_primary_0;
- private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_neutral_1000;
+ private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
+ private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000;
// Size, in bytes, of an entry in the array of colors in an ARSC file.
private static final int ARSC_ENTRY_SIZE = 16;
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 3709aa1..9789d70 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -15,6 +15,7 @@
*/
package android.window;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import android.annotation.ColorInt;
@@ -32,6 +33,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -183,6 +185,7 @@
* Create SplashScreenWindowView object from materials.
*/
public SplashScreenView build() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#build");
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
final SplashScreenView view = (SplashScreenView)
layoutInflater.inflate(R.layout.splash_screen_view, null, false);
@@ -208,14 +211,14 @@
view.mParceledIconBitmap = mParceledIconBitmap;
}
// branding image
- if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) {
+ if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0 && mBrandingDrawable != null) {
final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
params.width = mBrandingImageWidth;
params.height = mBrandingImageHeight;
view.mBrandingImageView.setLayoutParams(params);
- }
- if (mBrandingDrawable != null) {
view.mBrandingImageView.setBackground(mBrandingDrawable);
+ } else {
+ view.mBrandingImageView.setVisibility(GONE);
}
if (mParceledBrandingBitmap != null) {
view.mParceledBrandingBitmap = mParceledBrandingBitmap;
@@ -226,6 +229,7 @@
+ view.mBrandingImageView + " drawable: " + mBrandingDrawable
+ " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return view;
}
}
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index 26af615..b76ef0f 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -28,11 +28,9 @@
import android.content.pm.ResolveInfo;
import android.os.Message;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -62,11 +60,6 @@
// back to using the ResolverRankerService.
private ResolverRankerServiceResolverComparator mResolverRankerService;
- private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- true);
-
AppPredictionServiceResolverComparator(
Context context,
Intent intent,
@@ -183,9 +176,6 @@
if (mResolverRankerService != null) {
return mResolverRankerService.getScore(name);
}
- if (mAppendDirectShareEnabled && !mTargetScores.isEmpty()) {
- return mTargetScores.get(name);
- }
Integer rank = mTargetRanks.get(name);
if (rank == null) {
Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index d4d8536..862c900 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -256,15 +256,6 @@
SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
DEFAULT_SALT_EXPIRATION_DAYS);
- private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- true);
- private boolean mChooserTargetRankingEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.CHOOSER_TARGET_RANKING_ENABLED,
- true);
-
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
private IntentSender mRefinementIntentSender;
@@ -472,16 +463,10 @@
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
- private static final int CHOOSER_TARGET_RANKING_SCORE = 7;
private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
- private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500;
- private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT,
- DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS);
-
private boolean mMinTimeoutPassed = false;
private void removeAllMessages() {
@@ -491,7 +476,6 @@
removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
- removeMessages(CHOOSER_TARGET_RANKING_SCORE);
}
private void restartServiceRequestTimer() {
@@ -501,14 +485,13 @@
if (DEBUG) {
Log.d(TAG, "queryTargets setting watchdog timer for "
- + mDirectShareTimeout + "-"
+ WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
}
sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
WATCHDOG_TIMEOUT_MIN_MILLIS);
sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
- mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS);
+ WATCHDOG_TIMEOUT_MAX_MILLIS);
}
private void maybeStopServiceRequestTimer() {
@@ -608,17 +591,6 @@
getChooserActivityLogger().logSharesheetDirectLoadComplete();
break;
- case CHOOSER_TARGET_RANKING_SCORE:
- if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_RANKING_SCORE");
- final ChooserTargetRankingInfo scoreInfo = (ChooserTargetRankingInfo) msg.obj;
- ChooserListAdapter adapterForUserHandle =
- mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
- scoreInfo.userHandle);
- if (adapterForUserHandle != null) {
- adapterForUserHandle.addChooserTargetRankingScore(scoreInfo.scores);
- }
- break;
-
default:
super.handleMessage(msg);
}
@@ -878,24 +850,14 @@
final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
new ArrayList<>();
- // Separate ChooserTargets ranking scores and ranked Shortcuts.
List<AppTarget> shortcutResults = new ArrayList<>();
- List<AppTarget> chooserTargetScores = new ArrayList<>();
for (AppTarget appTarget : resultList) {
if (appTarget.getShortcutInfo() == null) {
continue;
}
- if (appTarget.getShortcutInfo().getId().equals(CHOOSER_TARGET)) {
- chooserTargetScores.add(appTarget);
- } else {
- shortcutResults.add(appTarget);
- }
+ shortcutResults.add(appTarget);
}
resultList = shortcutResults;
- if (mChooserTargetRankingEnabled) {
- sendChooserTargetRankingScore(chooserTargetScores,
- chooserListAdapter.getUserHandle());
- }
for (AppTarget appTarget : resultList) {
shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
appTarget.getShortcutInfo(),
@@ -2148,14 +2110,6 @@
return true;
}
- private void sendChooserTargetRankingScore(List<AppTarget> chooserTargetScores,
- UserHandle userHandle) {
- final Message msg = Message.obtain();
- msg.what = ChooserHandler.CHOOSER_TARGET_RANKING_SCORE;
- msg.obj = new ChooserTargetRankingInfo(chooserTargetScores, userHandle);
- mChooserHandler.sendMessage(msg);
- }
-
private void sendShareShortcutInfoList(
List<ShortcutManager.ShareShortcutInfo> resultList,
ChooserListAdapter chooserListAdapter,
@@ -2376,9 +2330,6 @@
}
private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
- if (!mChooserTargetRankingEnabled) {
- return;
- }
AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
if (directShareAppPredictor == null) {
@@ -2399,11 +2350,6 @@
targetIds.add(new AppTargetId(
String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
SHORTCUT_TARGET)));
- } else {
- String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString());
- targetIds.add(new AppTargetId(
- String.format("%s/%s/%s", titleHash, componentName.flattenToString(),
- CHOOSER_TARGET)));
}
}
directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
@@ -2423,29 +2369,6 @@
if (mDirectShareAppTargetCache != null) {
appTarget = mDirectShareAppTargetCache.get(chooserTarget);
}
- if (mChooserTargetRankingEnabled && appTarget == null) {
- // Send ChooserTarget sharing info to AppPredictor.
- ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault(
- chooserTarget.getComponentName(), chooserTarget.getComponentName());
- try {
- appTarget = new AppTarget.Builder(
- new AppTargetId(componentName.flattenToString()),
- new ShortcutInfo.Builder(
- createPackageContextAsUser(
- componentName.getPackageName(),
- 0 /* flags */,
- getUser()),
- CHOOSER_TARGET)
- .setActivity(componentName)
- .setShortLabel(ChooserUtil.md5(chooserTarget.getTitle().toString()))
- .build())
- .setClassName(componentName.getClassName())
- .build();
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not look up service " + componentName
- + "; component name not found");
- }
- }
// This is a direct share click that was provided by the APS
if (appTarget != null) {
directShareAppPredictor.notifyAppTargetEvent(
@@ -3971,9 +3894,7 @@
// until they start to scroll
ChooserListAdapter adapter =
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
- int validTargets =
- mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
- : adapter.getSelectableServiceTargetCount();
+ int validTargets = adapter.getSelectableServiceTargetCount();
if (validTargets <= maxTargetsPerRow) {
mHideDirectShareExpansion = true;
return;
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 5700668..cc2b12a 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -21,7 +21,6 @@
import android.app.ActivityManager;
import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -37,7 +36,6 @@
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
import android.util.Log;
-import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
@@ -53,21 +51,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
public class ChooserListAdapter extends ResolverListAdapter {
private static final String TAG = "ChooserListAdapter";
private static final boolean DEBUG = false;
- private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- true);
-
private boolean mEnableStackedApps = true;
public static final int NO_POSITION = -1;
@@ -79,8 +69,6 @@
private static final int MAX_SUGGESTED_APP_TARGETS = 4;
private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
- private static final int MAX_SERVICE_TARGET_APP = 8;
- private static final int DEFAULT_DIRECT_SHARE_RANKING_SCORE = 1000;
/** {@link #getBaseScore} */
public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
@@ -94,17 +82,11 @@
private int mNumShortcutResults = 0;
private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+ private boolean mApplySharingAppLimits;
// Reserve spots for incoming direct share targets by adding placeholders
private ChooserTargetInfo
mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
- private int mValidServiceTargetsNum = 0;
- private int mAvailableServiceTargetsNum = 0;
- private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>>
- mParkingDirectShareTargets = new HashMap<>();
- private final Map<ComponentName, Map<String, Integer>> mChooserTargetScores = new HashMap<>();
- private Set<ComponentName> mPendingChooserTargetService = new HashSet<>();
- private Set<ComponentName> mShortcutComponents = new HashSet<>();
private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();
@@ -183,6 +165,10 @@
if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
}
}
+ mApplySharingAppLimits = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+ true);
}
AppPredictor getAppPredictor() {
@@ -209,9 +195,6 @@
void refreshListView() {
if (mListViewDataChanged) {
- if (mAppendDirectShareEnabled) {
- appendServiceTargetsWithQuota();
- }
super.notifyDataSetChanged();
}
mListViewDataChanged = false;
@@ -221,10 +204,6 @@
private void createPlaceHolders() {
mNumShortcutResults = 0;
mServiceTargets.clear();
- mValidServiceTargetsNum = 0;
- mParkingDirectShareTargets.clear();
- mPendingChooserTargetService.clear();
- mShortcutComponents.clear();
for (int i = 0; i < mChooserListCommunicator.getMaxRankedTargets(); i++) {
mServiceTargets.add(mPlaceHolderTargetInfo);
}
@@ -517,33 +496,30 @@
+ targets.size()
+ " targets");
}
- if (mAppendDirectShareEnabled) {
- parkTargetIntoMemory(origTarget, targets, targetType, directShareToShortcutInfos,
- pendingChooserTargetServiceConnections);
- return;
- }
if (targets.size() == 0) {
return;
}
-
final float baseScore = getBaseScore(origTarget, targetType);
Collections.sort(targets, mBaseTargetComparator);
-
final boolean isShortcutResult =
(targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
|| targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
: MAX_CHOOSER_TARGETS_PER_APP;
+ final int targetsLimit = mApplySharingAppLimits ? Math.min(targets.size(), maxTargets)
+ : targets.size();
float lastScore = 0;
boolean shouldNotify = false;
- for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
+ for (int i = 0, count = targetsLimit; i < count; i++) {
final ChooserTarget target = targets.get(i);
float targetScore = target.getScore();
- targetScore *= baseScore;
- if (i > 0 && targetScore >= lastScore) {
- // Apply a decay so that the top app can't crowd out everything else.
- // This incents ChooserTargetServices to define what's truly better.
- targetScore = lastScore * 0.95f;
+ if (mApplySharingAppLimits) {
+ targetScore *= baseScore;
+ if (i > 0 && targetScore >= lastScore) {
+ // Apply a decay so that the top app can't crowd out everything else.
+ // This incents ChooserTargetServices to define what's truly better.
+ targetScore = lastScore * 0.95f;
+ }
}
UserHandle userHandle = getUserHandle();
Context contextAsUser = mContext.createContextAsUser(userHandle, 0 /* flags */);
@@ -561,7 +537,8 @@
Log.d(TAG, " => " + target.toString() + " score=" + targetScore
+ " base=" + target.getScore()
+ " lastScore=" + lastScore
- + " baseScore=" + baseScore);
+ + " baseScore=" + baseScore
+ + " applyAppLimit=" + mApplySharingAppLimits);
}
lastScore = targetScore;
@@ -573,217 +550,13 @@
}
/**
- * Store ChooserTarget ranking scores info wrapped in {@code targets}.
- */
- public void addChooserTargetRankingScore(List<AppTarget> targets) {
- Log.i(TAG, "addChooserTargetRankingScore " + targets.size() + " targets score.");
- for (AppTarget target : targets) {
- if (target.getShortcutInfo() == null) {
- continue;
- }
- ShortcutInfo shortcutInfo = target.getShortcutInfo();
- if (!shortcutInfo.getId().equals(ChooserActivity.CHOOSER_TARGET)
- || shortcutInfo.getActivity() == null) {
- continue;
- }
- ComponentName componentName = shortcutInfo.getActivity();
- if (!mChooserTargetScores.containsKey(componentName)) {
- mChooserTargetScores.put(componentName, new HashMap<>());
- }
- mChooserTargetScores.get(componentName).put(shortcutInfo.getShortLabel().toString(),
- target.getRank());
- }
- mChooserTargetScores.keySet().forEach(key -> rankTargetsWithinComponent(key));
- }
-
- /**
- * Rank chooserTargets of the given {@code componentName} in mParkingDirectShareTargets as per
- * available scores stored in mChooserTargetScores.
- */
- private void rankTargetsWithinComponent(ComponentName componentName) {
- if (!mParkingDirectShareTargets.containsKey(componentName)
- || !mChooserTargetScores.containsKey(componentName)) {
- return;
- }
- Map<String, Integer> scores = mChooserTargetScores.get(componentName);
- Collections.sort(mParkingDirectShareTargets.get(componentName).first, (o1, o2) -> {
- // The score has been normalized between 0 and 2000, the default is 1000.
- int score1 = scores.getOrDefault(
- ChooserUtil.md5(o1.getChooserTarget().getTitle().toString()),
- DEFAULT_DIRECT_SHARE_RANKING_SCORE);
- int score2 = scores.getOrDefault(
- ChooserUtil.md5(o2.getChooserTarget().getTitle().toString()),
- DEFAULT_DIRECT_SHARE_RANKING_SCORE);
- return score2 - score1;
- });
- }
-
- /**
- * Park {@code targets} into memory for the moment to surface them later when view is refreshed.
- * Components pending on ChooserTargetService query are also recorded.
- */
- private void parkTargetIntoMemory(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
- @ChooserActivity.ShareTargetType int targetType,
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
- List<ChooserActivity.ChooserTargetServiceConnection>
- pendingChooserTargetServiceConnections) {
- ComponentName origComponentName = origTarget != null ? origTarget.getResolvedComponentName()
- : !targets.isEmpty() ? targets.get(0).getComponentName() : null;
- Log.i(TAG,
- "parkTargetIntoMemory " + origComponentName + ", " + targets.size() + " targets");
- mPendingChooserTargetService = pendingChooserTargetServiceConnections.stream()
- .map(ChooserActivity.ChooserTargetServiceConnection::getComponentName)
- .filter(componentName -> !componentName.equals(origComponentName))
- .collect(Collectors.toSet());
- // Park targets in memory
- if (!targets.isEmpty()) {
- final boolean isShortcutResult =
- (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
- || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
- Context contextAsUser = mContext.createContextAsUser(getUserHandle(),
- 0 /* flags */);
- List<ChooserTargetInfo> parkingTargetInfos = targets.stream()
- .map(target ->
- new SelectableTargetInfo(
- contextAsUser, origTarget, target, target.getScore(),
- mSelectableTargetInfoCommunicator,
- (isShortcutResult ? directShareToShortcutInfos.get(target)
- : null))
- )
- .collect(Collectors.toList());
- Pair<List<ChooserTargetInfo>, Integer> parkingTargetInfoPair =
- mParkingDirectShareTargets.getOrDefault(origComponentName,
- new Pair<>(new ArrayList<>(), 0));
- for (ChooserTargetInfo target : parkingTargetInfos) {
- if (!checkDuplicateTarget(target, parkingTargetInfoPair.first)
- && !checkDuplicateTarget(target, mServiceTargets)) {
- parkingTargetInfoPair.first.add(target);
- mAvailableServiceTargetsNum++;
- }
- }
- mParkingDirectShareTargets.put(origComponentName, parkingTargetInfoPair);
- rankTargetsWithinComponent(origComponentName);
- if (isShortcutResult) {
- mShortcutComponents.add(origComponentName);
- }
- }
- notifyDataSetChanged();
- }
-
- /**
- * Append targets of top ranked share app into direct share row with quota limit. Remove
- * appended ones from memory.
- */
- private void appendServiceTargetsWithQuota() {
- int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets();
- List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets);
- float totalScore = 0f;
- for (ComponentName component : topComponentNames) {
- if (!mPendingChooserTargetService.contains(component)
- && !mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- totalScore += super.getScore(component);
- }
- boolean shouldWaitPendingService = false;
- for (ComponentName component : topComponentNames) {
- if (!mPendingChooserTargetService.contains(component)
- && !mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- float score = super.getScore(component);
- int quota = Math.round(maxRankedTargets * score / totalScore);
- if (mPendingChooserTargetService.contains(component) && quota >= 1) {
- shouldWaitPendingService = true;
- }
- if (!mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- // Append targets into direct share row as per quota.
- Pair<List<ChooserTargetInfo>, Integer> parkingTargetsItem =
- mParkingDirectShareTargets.get(component);
- List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first;
- int insertedNum = parkingTargetsItem.second;
- while (insertedNum < quota && !parkingTargets.isEmpty()) {
- if (!checkDuplicateTarget(parkingTargets.get(0), mServiceTargets)) {
- mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0));
- mValidServiceTargetsNum++;
- insertedNum++;
- }
- parkingTargets.remove(0);
- }
- Log.i(TAG, " appendServiceTargetsWithQuota component=" + component
- + " appendNum=" + (insertedNum - parkingTargetsItem.second));
- if (DEBUG) {
- Log.d(TAG, " appendServiceTargetsWithQuota component=" + component
- + " score=" + score
- + " totalScore=" + totalScore
- + " quota=" + quota);
- }
- mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum));
- }
- if (!shouldWaitPendingService) {
- fillAllServiceTargets();
- }
- }
-
- /**
- * Append all remaining targets (parking in memory) into direct share row as per their ranking.
- */
- private void fillAllServiceTargets() {
- if (mParkingDirectShareTargets.isEmpty()) {
- return;
- }
- Log.i(TAG, " fillAllServiceTargets");
- List<ComponentName> topComponentNames = getTopComponentNames(MAX_SERVICE_TARGET_APP);
- // Append all remaining targets of top recommended components into direct share row.
- for (ComponentName component : topComponentNames) {
- if (!mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- mParkingDirectShareTargets.get(component).first.stream()
- .filter(target -> !checkDuplicateTarget(target, mServiceTargets))
- .forEach(target -> {
- mServiceTargets.add(mValidServiceTargetsNum, target);
- mValidServiceTargetsNum++;
- });
- mParkingDirectShareTargets.remove(component);
- }
- // Append all remaining shortcuts targets into direct share row.
- mParkingDirectShareTargets.entrySet().stream()
- .filter(entry -> mShortcutComponents.contains(entry.getKey()))
- .map(entry -> entry.getValue())
- .map(pair -> pair.first)
- .forEach(targets -> {
- for (ChooserTargetInfo target : targets) {
- if (!checkDuplicateTarget(target, mServiceTargets)) {
- mServiceTargets.add(mValidServiceTargetsNum, target);
- mValidServiceTargetsNum++;
- }
- }
- });
- mParkingDirectShareTargets.clear();
- }
-
- private boolean checkDuplicateTarget(ChooserTargetInfo target,
- List<ChooserTargetInfo> destination) {
- // Check for duplicates and abort if found
- for (ChooserTargetInfo otherTargetInfo : destination) {
- if (target.isSimilar(otherTargetInfo)) {
- return true;
- }
- }
- return false;
- }
-
- /**
* The return number have to exceed a minimum limit to make direct share area expandable. When
* append direct share targets is enabled, return count of all available targets parking in the
* memory; otherwise, it is shortcuts count which will help reduce the amount of visible
* shuffling due to older-style direct share targets.
*/
int getNumServiceTargetsForExpand() {
- return mAppendDirectShareEnabled ? mAvailableServiceTargetsNum : mNumShortcutResults;
+ return mNumShortcutResults;
}
/**
@@ -801,16 +574,11 @@
if (target == null) {
return CALLER_TARGET_SCORE_BOOST;
}
-
- if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
- return SHORTCUT_TARGET_SCORE_BOOST;
- }
-
float score = super.getScore(target);
- if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
+ if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
+ || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
return score * SHORTCUT_TARGET_SCORE_BOOST;
}
-
return score;
}
@@ -820,9 +588,6 @@
*/
public void completeServiceTargetLoading() {
mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
- if (mAppendDirectShareEnabled) {
- fillAllServiceTargets();
- }
if (mServiceTargets.isEmpty()) {
mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 52801fa..a5b894d 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -130,12 +130,6 @@
// Flags related to media notifications
/**
- * (boolean) If {@code true}, enables the seekbar in compact media notifications.
- */
- public static final String COMPACT_MEDIA_SEEKBAR_ENABLED =
- "compact_media_notification_seekbar_enabled";
-
- /**
* (int) Maximum number of days to retain the salt for hashing direct share targets in logging
*/
public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days";
@@ -415,6 +409,12 @@
public static final String CHOOSER_TARGET_RANKING_ENABLED = "chooser_target_ranking_enabled";
/**
+ * (boolean) Whether dark launch of remote prediction service is enabled.
+ */
+ public static final String DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED =
+ "dark_launch_remote_prediction_service_enabled";
+
+ /**
* (boolean) Whether to enable pinch resizing for PIP.
*/
public static final String PIP_PINCH_RESIZE = "pip_pinch_resize";
@@ -473,6 +473,14 @@
*/
public static final String SHARE_USE_SERVICE_TARGETS = "share_use_service_targets";
+ /**
+ * (boolean) If true, SysUI provides guardrails for app usage of Direct Share by enforcing
+ * limits on number of targets per app & adjusting scores for apps providing many targets. If
+ * false, this step is skipped. This should be true unless the ranking provider configured by
+ * [some other flag] is expected to manage these incentives.
+ */
+ public static final String APPLY_SHARING_APP_LIMITS_IN_SYSUI =
+ "apply_sharing_app_limits_in_sysui";
/*
* (long) The duration that the home button must be pressed before triggering Assist
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 7f87885..a043756 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -89,6 +89,7 @@
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
@@ -12548,81 +12549,6 @@
}
/**
- * SparseDoubleArray map integers to doubles.
- * Its implementation is the same as that of {@link SparseLongArray}; see there for details.
- *
- * @see SparseLongArray
- */
- private static class SparseDoubleArray {
- /**
- * The int->double map, but storing the doubles as longs using
- * {@link Double.doubleToRawLongBits(double)}.
- */
- private final SparseLongArray mValues = new SparseLongArray();
-
- /**
- * Gets the double mapped from the specified key, or <code>0</code>
- * if no such mapping has been made.
- */
- public double get(int key) {
- if (mValues.indexOfKey(key) >= 0) {
- return Double.longBitsToDouble(mValues.get(key));
- }
- return 0;
- }
-
- /**
- * Adds a mapping from the specified key to the specified value,
- * replacing the previous mapping from the specified key if there
- * was one.
- */
- public void put(int key, double value) {
- mValues.put(key, Double.doubleToRawLongBits(value));
- }
-
- /**
- * Adds a mapping from the specified key to the specified value,
- * <b>adding</b> to the previous mapping from the specified key if there
- * was one.
- */
- public void add(int key, double summand) {
- final double oldValue = get(key);
- put(key, oldValue + summand);
- }
-
- /**
- * Returns the number of key-value mappings that this SparseDoubleArray
- * currently stores.
- */
- public int size() {
- return mValues.size();
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the key from the <code>index</code>th key-value mapping that this
- * SparseDoubleArray stores.
- *
- * @see SparseLongArray#keyAt(int)
- */
- public int keyAt(int index) {
- return mValues.keyAt(index);
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the value from the <code>index</code>th key-value mapping that this
- * SparseDoubleArray stores.
- *
- * @see SparseLongArray#valueAt(int)
- */
- public double valueAt(int index) {
- return Double.longBitsToDouble(mValues.valueAt(index));
- }
-
- }
-
- /**
* Read and record Rail Energy data.
*/
public void updateRailStatsLocked() {
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index ec9a0a2..49dbbaa 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -28,7 +28,6 @@
void setActive(boolean active, boolean fullscreen, boolean reportToImeController);
void scheduleStartInputIfNecessary(boolean fullscreen);
void reportFullscreenMode(boolean fullscreen);
- void applyImeVisibility(boolean setVisible);
void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues);
void setImeTraceEnabled(boolean enabled);
}
diff --git a/core/java/com/android/internal/widget/MediaNotificationView.java b/core/java/com/android/internal/widget/MediaNotificationView.java
index f42d5da..8ff3c10 100644
--- a/core/java/com/android/internal/widget/MediaNotificationView.java
+++ b/core/java/com/android/internal/widget/MediaNotificationView.java
@@ -19,31 +19,19 @@
import android.annotation.Nullable;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.NotificationHeaderView;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.RemoteViews;
import java.util.ArrayList;
/**
- * A TextView that can float around an image on the end.
+ * The Layout class which handles template details for the Notification.MediaStyle
*
* @hide
*/
@RemoteViews.RemoteView
public class MediaNotificationView extends FrameLayout {
- private final int mNotificationContentMarginEnd;
- private final int mNotificationContentImageMarginEnd;
- private ImageView mRightIcon;
- private View mActions;
- private NotificationHeaderView mHeader;
- private View mMainColumn;
- private View mMediaContent;
- private int mImagePushIn;
private ArrayList<VisibilityChangeListener> mListeners;
public MediaNotificationView(Context context) {
@@ -58,120 +46,14 @@
this(context, attrs, defStyleAttr, 0);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- boolean hasIcon = mRightIcon.getVisibility() != GONE;
- if (!hasIcon) {
- resetHeaderIndention();
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int mode = MeasureSpec.getMode(widthMeasureSpec);
- boolean reMeasure = false;
- mImagePushIn = 0;
- if (hasIcon && mode != MeasureSpec.UNSPECIFIED) {
- int size = MeasureSpec.getSize(widthMeasureSpec);
- size = size - mActions.getMeasuredWidth();
- ViewGroup.MarginLayoutParams layoutParams =
- (MarginLayoutParams) mRightIcon.getLayoutParams();
- int imageEndMargin = layoutParams.getMarginEnd();
- size -= imageEndMargin;
- int fullHeight = mMediaContent.getMeasuredHeight();
- if (size > fullHeight) {
- size = fullHeight;
- } else if (size < fullHeight) {
- size = Math.max(0, size);
- mImagePushIn = fullHeight - size;
- }
- if (layoutParams.width != fullHeight || layoutParams.height != fullHeight) {
- layoutParams.width = fullHeight;
- layoutParams.height = fullHeight;
- mRightIcon.setLayoutParams(layoutParams);
- reMeasure = true;
- }
-
- // lets ensure that the main column doesn't run into the image
- ViewGroup.MarginLayoutParams params
- = (MarginLayoutParams) mMainColumn.getLayoutParams();
- int marginEnd = size + imageEndMargin + mNotificationContentMarginEnd;
- if (marginEnd != params.getMarginEnd()) {
- params.setMarginEnd(marginEnd);
- mMainColumn.setLayoutParams(params);
- reMeasure = true;
- }
- // TODO(b/172652345): validate all this logic (especially positioning of expand button)
- // margin for the entire header line
- int headerMarginEnd = imageEndMargin;
- // margin for the header text (not including the expand button and other icons)
- int headerExtraMarginEnd = Math.max(0,
- size + imageEndMargin - mHeader.getTopLineBaseMarginEnd());
- if (headerExtraMarginEnd != mHeader.getTopLineExtraMarginEnd()) {
- mHeader.setTopLineExtraMarginEnd(headerExtraMarginEnd);
- reMeasure = true;
- }
- params = (MarginLayoutParams) mHeader.getLayoutParams();
- if (params.getMarginEnd() != headerMarginEnd) {
- params.setMarginEnd(headerMarginEnd);
- mHeader.setLayoutParams(params);
- reMeasure = true;
- }
- if (mHeader.getPaddingEnd() != mNotificationContentImageMarginEnd) {
- mHeader.setPaddingRelative(mHeader.getPaddingStart(),
- mHeader.getPaddingTop(),
- mNotificationContentImageMarginEnd,
- mHeader.getPaddingBottom());
- reMeasure = true;
- }
- }
- if (reMeasure) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mImagePushIn > 0) {
- if (this.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- mImagePushIn *= -1;
- }
- mRightIcon.layout(mRightIcon.getLeft() + mImagePushIn, mRightIcon.getTop(),
- mRightIcon.getRight() + mImagePushIn, mRightIcon.getBottom());
- }
- }
-
- private void resetHeaderIndention() {
- if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) {
- mHeader.setPaddingRelative(mHeader.getPaddingStart(),
- mHeader.getPaddingTop(),
- mNotificationContentMarginEnd,
- mHeader.getPaddingBottom());
- }
- ViewGroup.MarginLayoutParams headerParams =
- (MarginLayoutParams) mHeader.getLayoutParams();
- headerParams.setMarginEnd(0);
- if (headerParams.getMarginEnd() != 0) {
- headerParams.setMarginEnd(0);
- mHeader.setLayoutParams(headerParams);
- }
- }
-
public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mNotificationContentMarginEnd = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_end);
- mNotificationContentImageMarginEnd = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_image_margin_end);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mRightIcon = findViewById(com.android.internal.R.id.right_icon);
- mActions = findViewById(com.android.internal.R.id.media_actions);
- mHeader = findViewById(com.android.internal.R.id.notification_header);
- mMainColumn = findViewById(com.android.internal.R.id.notification_main_column);
- mMediaContent = findViewById(com.android.internal.R.id.notification_media_content);
}
@Override
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f49a834..f1fa5db 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -238,6 +238,7 @@
"android.hardware.camera.device@3.2",
"media_permission-aidl-cpp",
"libandroidicu",
+ "libandroid_net",
"libbpf_android",
"libnetdbpf",
"libnetdutils",
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index b790056..b46b5a2 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -68,7 +68,7 @@
};
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
- jlong width, jlong height, jint format, jboolean enableTripleBuffering) {
+ jlong width, jlong height, jint format) {
String8 str8;
if (jName) {
const jchar* str16 = env->GetStringCritical(jName, nullptr);
@@ -81,7 +81,7 @@
std::string name = str8.string();
sp<BLASTBufferQueue> queue =
new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
- height, format, enableTripleBuffering);
+ height, format);
queue->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(queue.get());
}
@@ -140,7 +140,7 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
- {"nativeCreate", "(Ljava/lang/String;JJJIZ)J", (void*)nativeCreate},
+ {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
{"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
{"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction},
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index d4b5c2b..dd62bb1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -52,6 +52,7 @@
#include <string.h>
#include <sys/epoll.h>
#include <sys/errno.h>
+#include <sys/pidfd.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
@@ -1316,14 +1317,8 @@
return removeAllProcessGroups();
}
-// Wrapper function to the syscall pidfd_open, which creates a file
-// descriptor that refers to the process whose PID is specified in pid.
-static inline int sys_pidfd_open(pid_t pid, unsigned int flags) {
- return syscall(__NR_pidfd_open, pid, flags);
-}
-
static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
- int fd = sys_pidfd_open(pid, flags);
+ int fd = pidfd_open(pid, flags);
if (fd < 0) {
jniThrowErrnoException(env, "nativePidFdOpen", errno);
return -1;
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 52d21a8..10927b9 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -18,28 +18,28 @@
//#define LOG_NDEBUG 0
-#include <nativehelper/JNIHelp.h>
-
#include <android_runtime/AndroidRuntime.h>
-#include <log/log.h>
-#include <utils/Looper.h>
#include <input/InputTransport.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Looper.h>
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
#include "android_view_KeyEvent.h"
#include "android_view_MotionEvent.h"
+#include "core_jni_helpers.h"
-#include <nativehelper/ScopedLocalRef.h>
+#include <inttypes.h>
#include <unordered_map>
-#include "core_jni_helpers.h"
using android::base::Result;
namespace android {
// Log debug messages about the dispatch cycle.
-static const bool kDebugDispatchCycle = false;
+static constexpr bool kDebugDispatchCycle = false;
static struct {
jclass clazz;
@@ -74,8 +74,10 @@
return mInputPublisher.getChannel()->getName();
}
- virtual int handleEvent(int receiveFd, int events, void* data);
+ int handleEvent(int receiveFd, int events, void* data) override;
status_t receiveFinishedSignals(JNIEnv* env);
+ bool notifyFinishedSignal(JNIEnv* env, jobject sender, const InputPublisher::Finished& finished,
+ bool skipCallbacks);
};
NativeInputEventSender::NativeInputEventSender(JNIEnv* env, jobject senderWeak,
@@ -196,8 +198,13 @@
ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str());
}
- ScopedLocalRef<jobject> senderObj(env, NULL);
- bool skipCallbacks = false;
+ ScopedLocalRef<jobject> senderObj(env, jniGetReferent(env, mSenderWeakGlobal));
+ if (!senderObj.get()) {
+ ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
+ getInputChannelName().c_str());
+ return DEAD_OBJECT;
+ }
+ bool skipCallbacks = false; // stop calling Java functions after an exception occurs
for (;;) {
Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal();
if (!result.ok()) {
@@ -206,45 +213,55 @@
return OK;
}
ALOGE("channel '%s' ~ Failed to consume finished signals. status=%d",
- getInputChannelName().c_str(), status);
+ getInputChannelName().c_str(), status);
return status;
}
- auto it = mPublishedSeqMap.find(result->seq);
- if (it == mPublishedSeqMap.end()) {
- continue;
- }
-
- uint32_t seq = it->second;
- mPublishedSeqMap.erase(it);
-
- if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
- getInputChannelName().c_str(), seq, result->handled ? "true" : "false",
- mPublishedSeqMap.size());
- }
-
- if (!skipCallbacks) {
- if (!senderObj.get()) {
- senderObj.reset(jniGetReferent(env, mSenderWeakGlobal));
- if (!senderObj.get()) {
- ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
- getInputChannelName().c_str());
- return DEAD_OBJECT;
- }
- }
-
- env->CallVoidMethod(senderObj.get(),
- gInputEventSenderClassInfo.dispatchInputEventFinished,
- static_cast<jint>(seq), static_cast<jboolean>(result->handled));
- if (env->ExceptionCheck()) {
- ALOGE("Exception dispatching finished signal.");
- skipCallbacks = true;
- }
+ const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks);
+ if (!notified) {
+ skipCallbacks = true;
}
}
}
+/**
+ * Invoke the Java function dispatchInputEventFinished for the received "Finished" signal.
+ * Set the variable 'skipCallbacks' to 'true' if a Java exception occurred.
+ * Java function will only be called if 'skipCallbacks' is originally 'false'.
+ *
+ * Return "false" if an exception occurred while calling the Java function
+ * "true" otherwise
+ */
+bool NativeInputEventSender::notifyFinishedSignal(JNIEnv* env, jobject sender,
+ const InputPublisher::Finished& finished,
+ bool skipCallbacks) {
+ auto it = mPublishedSeqMap.find(finished.seq);
+ if (it == mPublishedSeqMap.end()) {
+ ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq);
+ // Since this is coming from the receiver (typically app), it's possible that an app
+ // does something wrong and sends bad data. Just ignore and process other events.
+ return true;
+ }
+ const uint32_t seq = it->second;
+ mPublishedSeqMap.erase(it);
+
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
+ getInputChannelName().c_str(), seq, finished.handled ? "true" : "false",
+ mPublishedSeqMap.size());
+ }
+ if (skipCallbacks) {
+ return true;
+ }
+
+ env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished,
+ static_cast<jint>(seq), static_cast<jboolean>(finished.handled));
+ if (env->ExceptionCheck()) {
+ ALOGE("Exception dispatching finished signal for seq=%" PRIu32, seq);
+ return false;
+ }
+ return true;
+}
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject senderWeak,
jobject inputChannelObj, jobject messageQueueObj) {
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index bbb0edd..4f3ae28 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -178,4 +178,6 @@
CM_DETECT_INTERACTION = 13;
CM_INVALIDATION_REQUESTER = 14;
CM_INVALIDATE = 15;
+ CM_STOP_USER = 16;
+ CM_START_USER = 17;
}
\ No newline at end of file
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index aab054f..16c3ab0 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -21,40 +21,38 @@
import "frameworks/base/core/proto/android/privacy.proto";
-message OneShotProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 duration = 1;
- repeated int32 amplitude = 2;
-}
-
-message WaveformProto {
+message StepSegmentProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 timings = 1;
- repeated int32 amplitudes = 2;
- required bool repeat = 3;
+ optional int32 duration = 1;
+ optional float amplitude = 2;
}
-message PrebakedProto {
+message PrebakedSegmentProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 effect_id = 1;
optional int32 effect_strength = 2;
optional int32 fallback = 3;
}
-message ComposedProto {
+message PrimitiveSegmentProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 effect_ids = 1;
- repeated float effect_scales = 2;
- repeated int32 delays = 3;
+ optional int32 primitive_id = 1;
+ optional float scale = 2;
+ optional int32 delay = 3;
+}
+
+message SegmentProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional PrebakedSegmentProto prebaked = 1;
+ optional PrimitiveSegmentProto primitive = 2;
+ optional StepSegmentProto step = 3;
}
// A com.android.os.VibrationEffect object.
message VibrationEffectProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional OneShotProto oneshot = 1;
- optional WaveformProto waveform = 2;
- optional PrebakedProto prebaked = 3;
- optional ComposedProto composed = 4;
+ optional SegmentProto segments = 1;
+ required int32 repeat = 2;
}
message SyncVibrationEffectProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 45fd96a..8d7f542 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2659,10 +2659,6 @@
<permission android:name="android.permission.CREATE_USERS"
android:protectionLevel="signature" />
- <!-- @TestApi @hide Allows an application to query user info for all users on the device. -->
- <permission android:name="android.permission.QUERY_USERS"
- android:protectionLevel="signature" />
-
<!-- @hide Allows an application to set the profile owners and the device owner.
This permission is not available to third party applications.-->
<permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
@@ -3447,6 +3443,14 @@
<permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
android:protectionLevel="signature" />
+ <!-- Allows an application to avoid all toast rate limiting restrictions.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.UNLIMITED_TOASTS"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
<!-- @SystemApi Allows an application to use
{@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
to hide non-system-overlay windows.
@@ -3550,7 +3554,7 @@
@hide
-->
<permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|recents" />
<!-- Allows an application to retrieve the current state of keys and
switches.
@@ -4484,7 +4488,7 @@
<permission android:name="android.permission.FACTORY_TEST"
android:protectionLevel="signature" />
- <!-- @hide @TestApi Allows an application to broadcast the intent {@link
+ <!-- @hide @TestApi @SystemApi Allows an application to broadcast the intent {@link
android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
<p>Not for use by third-party applications.
-->
diff --git a/core/res/remote_color_resources_res/values/colors.xml b/core/res/remote_color_resources_res/values/colors.xml
index 295f16e..e4bcae43 100644
--- a/core/res/remote_color_resources_res/values/colors.xml
+++ b/core/res/remote_color_resources_res/values/colors.xml
@@ -1,40 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Note: the values of the colors doesn't really matter (they will always be overwritten before used), but they help a lot debugging, to find out which color is where in the ARSC file. -->
- <color name="system_primary_0">#01010101</color>
- <color name="system_primary_50">#02020202</color>
- <color name="system_primary_100">#03030303</color>
- <color name="system_primary_200">#04040404</color>
- <color name="system_primary_300">#05050505</color>
- <color name="system_primary_400">#06060606</color>
- <color name="system_primary_500">#07070707</color>
- <color name="system_primary_600">#08080808</color>
- <color name="system_primary_700">#09090909</color>
- <color name="system_primary_800">#0a0a0a0a</color>
- <color name="system_primary_900">#0b0b0b0b</color>
- <color name="system_primary_1000">#0c0c0c0c</color>
- <color name="system_secondary_0">#10101010</color>
- <color name="system_secondary_50">#20202020</color>
- <color name="system_secondary_100">#30303030</color>
- <color name="system_secondary_200">#40404040</color>
- <color name="system_secondary_300">#50505050</color>
- <color name="system_secondary_400">#60606060</color>
- <color name="system_secondary_500">#70707070</color>
- <color name="system_secondary_600">#80808080</color>
- <color name="system_secondary_700">#90909090</color>
- <color name="system_secondary_800">#a0a0a0a0</color>
- <color name="system_secondary_900">#b0b0b0b0</color>
- <color name="system_secondary_1000">#c0c0c0c0</color>
- <color name="system_neutral_0">#1f1f1f1f</color>
- <color name="system_neutral_50">#2f2f2f2f</color>
- <color name="system_neutral_100">#3f3f3f3f</color>
- <color name="system_neutral_200">#4f4f4f4f</color>
- <color name="system_neutral_300">#5f5f5f5f</color>
- <color name="system_neutral_400">#6f6f6f6f</color>
- <color name="system_neutral_500">#7f7f7f7f</color>
- <color name="system_neutral_600">#8f8f8f8f</color>
- <color name="system_neutral_700">#9f9f9f9f</color>
- <color name="system_neutral_800">#afafafaf</color>
- <color name="system_neutral_900">#bfbfbfbf</color>
- <color name="system_neutral_1000">#cfcfcfcf</color>
+ <color name="system_accent1_0">#ffffff</color>
+ <color name="system_accent1_50">#91fff4</color>
+ <color name="system_accent1_100">#83f6e5</color>
+ <color name="system_accent1_200">#65d9c9</color>
+ <color name="system_accent1_300">#45bdae</color>
+ <color name="system_accent1_400">#1fa293</color>
+ <color name="system_accent1_500">#008377</color>
+ <color name="system_accent1_600">#006d61</color>
+ <color name="system_accent1_700">#005449</color>
+ <color name="system_accent1_800">#003c33</color>
+ <color name="system_accent1_900">#00271e</color>
+ <color name="system_accent1_1000">#000000</color>
+ <color name="system_accent2_0">#ffffff</color>
+ <color name="system_accent2_50">#91fff4</color>
+ <color name="system_accent2_100">#83f6e5</color>
+ <color name="system_accent2_200">#65d9c9</color>
+ <color name="system_accent2_300">#45bdae</color>
+ <color name="system_accent2_400">#1fa293</color>
+ <color name="system_accent2_500">#008377</color>
+ <color name="system_accent2_600">#006d61</color>
+ <color name="system_accent2_700">#005449</color>
+ <color name="system_accent2_800">#003c33</color>
+ <color name="system_accent2_900">#00271e</color>
+ <color name="system_accent2_1000">#000000</color>
+ <color name="system_accent3_0">#ffffff</color>
+ <color name="system_accent3_50">#91fff4</color>
+ <color name="system_accent3_100">#83f6e5</color>
+ <color name="system_accent3_200">#65d9c9</color>
+ <color name="system_accent3_300">#45bdae</color>
+ <color name="system_accent3_400">#1fa293</color>
+ <color name="system_accent3_500">#008377</color>
+ <color name="system_accent3_600">#006d61</color>
+ <color name="system_accent3_700">#005449</color>
+ <color name="system_accent3_800">#003c33</color>
+ <color name="system_accent3_900">#00271e</color>
+ <color name="system_accent3_1000">#000000</color>
+ <color name="system_neutral1_0">#ffffff</color>
+ <color name="system_neutral1_50">#f0f0f0</color>
+ <color name="system_neutral1_100">#e2e2e2</color>
+ <color name="system_neutral1_200">#c6c6c6</color>
+ <color name="system_neutral1_300">#ababab</color>
+ <color name="system_neutral1_400">#909090</color>
+ <color name="system_neutral1_500">#757575</color>
+ <color name="system_neutral1_600">#5e5e5e</color>
+ <color name="system_neutral1_700">#464646</color>
+ <color name="system_neutral1_800">#303030</color>
+ <color name="system_neutral1_900">#1b1b1b</color>
+ <color name="system_neutral1_1000">#000000</color>
+ <color name="system_neutral2_0">#ffffff</color>
+ <color name="system_neutral2_50">#f0f0f0</color>
+ <color name="system_neutral2_100">#e2e2e2</color>
+ <color name="system_neutral2_200">#c6c6c6</color>
+ <color name="system_neutral2_300">#ababab</color>
+ <color name="system_neutral2_400">#909090</color>
+ <color name="system_neutral2_500">#757575</color>
+ <color name="system_neutral2_600">#5e5e5e</color>
+ <color name="system_neutral2_700">#464646</color>
+ <color name="system_neutral2_800">#303030</color>
+ <color name="system_neutral2_900">#1b1b1b</color>
+ <color name="system_neutral2_1000">#000000</color>
</resources>
diff --git a/core/res/remote_color_resources_res/values/public.xml b/core/res/remote_color_resources_res/values/public.xml
index e628f09..9616628 100644
--- a/core/res/remote_color_resources_res/values/public.xml
+++ b/core/res/remote_color_resources_res/values/public.xml
@@ -1,41 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<public-group type="color" first-id="0x0106001d">
- <public name="system_primary_0" />
- <public name="system_primary_50" />
- <public name="system_primary_100" />
- <public name="system_primary_200" />
- <public name="system_primary_300" />
- <public name="system_primary_400" />
- <public name="system_primary_500" />
- <public name="system_primary_600" />
- <public name="system_primary_700" />
- <public name="system_primary_800" />
- <public name="system_primary_900" />
- <public name="system_primary_1000" />
- <public name="system_secondary_0" />
- <public name="system_secondary_50" />
- <public name="system_secondary_100" />
- <public name="system_secondary_200" />
- <public name="system_secondary_300" />
- <public name="system_secondary_400" />
- <public name="system_secondary_500" />
- <public name="system_secondary_600" />
- <public name="system_secondary_700" />
- <public name="system_secondary_800" />
- <public name="system_secondary_900" />
- <public name="system_secondary_1000" />
- <public name="system_neutral_0" />
- <public name="system_neutral_50" />
- <public name="system_neutral_100" />
- <public name="system_neutral_200" />
- <public name="system_neutral_300" />
- <public name="system_neutral_400" />
- <public name="system_neutral_500" />
- <public name="system_neutral_600" />
- <public name="system_neutral_700" />
- <public name="system_neutral_800" />
- <public name="system_neutral_900" />
- <public name="system_neutral_1000" />
+ <public name="system_accent1_0" />
+ <public name="system_accent1_50" />
+ <public name="system_accent1_100" />
+ <public name="system_accent1_200" />
+ <public name="system_accent1_300" />
+ <public name="system_accent1_400" />
+ <public name="system_accent1_500" />
+ <public name="system_accent1_600" />
+ <public name="system_accent1_700" />
+ <public name="system_accent1_800" />
+ <public name="system_accent1_900" />
+ <public name="system_accent1_1000" />
+ <public name="system_accent2_0" />
+ <public name="system_accent2_50" />
+ <public name="system_accent2_100" />
+ <public name="system_accent2_200" />
+ <public name="system_accent2_300" />
+ <public name="system_accent2_400" />
+ <public name="system_accent2_500" />
+ <public name="system_accent2_600" />
+ <public name="system_accent2_700" />
+ <public name="system_accent2_800" />
+ <public name="system_accent2_900" />
+ <public name="system_accent2_1000" />
+ <public name="system_accent3_0" />
+ <public name="system_accent3_50" />
+ <public name="system_accent3_100" />
+ <public name="system_accent3_200" />
+ <public name="system_accent3_300" />
+ <public name="system_accent3_400" />
+ <public name="system_accent3_500" />
+ <public name="system_accent3_600" />
+ <public name="system_accent3_700" />
+ <public name="system_accent3_800" />
+ <public name="system_accent3_900" />
+ <public name="system_accent3_1000" />
+ <public name="system_neutral1_0" />
+ <public name="system_neutral1_50" />
+ <public name="system_neutral1_100" />
+ <public name="system_neutral1_200" />
+ <public name="system_neutral1_300" />
+ <public name="system_neutral1_400" />
+ <public name="system_neutral1_500" />
+ <public name="system_neutral1_600" />
+ <public name="system_neutral1_700" />
+ <public name="system_neutral1_800" />
+ <public name="system_neutral1_900" />
+ <public name="system_neutral1_1000" />
+ <public name="system_neutral2_0" />
+ <public name="system_neutral2_50" />
+ <public name="system_neutral2_100" />
+ <public name="system_neutral2_200" />
+ <public name="system_neutral2_300" />
+ <public name="system_neutral2_400" />
+ <public name="system_neutral2_500" />
+ <public name="system_neutral2_600" />
+ <public name="system_neutral2_700" />
+ <public name="system_neutral2_800" />
+ <public name="system_neutral2_900" />
+ <public name="system_neutral2_1000" />
</public-group>
</resources>
diff --git a/core/res/res/color/text_color_primary_device_default_dark.xml b/core/res/res/color/text_color_primary_device_default_dark.xml
index 90d6b07..5926fde 100644
--- a/core/res/res/color/text_color_primary_device_default_dark.xml
+++ b/core/res/res/color/text_color_primary_device_default_dark.xml
@@ -17,7 +17,6 @@
<!-- Please see primary_text_material_dark.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_50"/>
- <item android:color="@color/system_primary_50"/>
+ android:color="@color/system_neutral1_500"/>
+ <item android:color="@color/system_neutral1_50"/>
</selector>
diff --git a/core/res/res/color/text_color_primary_device_default_light.xml b/core/res/res/color/text_color_primary_device_default_light.xml
index bdc4fa9..1379523 100644
--- a/core/res/res/color/text_color_primary_device_default_light.xml
+++ b/core/res/res/color/text_color_primary_device_default_light.xml
@@ -17,7 +17,6 @@
<!-- Please see primary_text_material_light.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_900"/>
- <item android:color="@color/system_primary_900"/>
+ android:color="@color/system_neutral1_400"/>
+ <item android:color="@color/system_neutral1_900"/>
</selector>
diff --git a/core/res/res/color/text_color_secondary_device_default_dark.xml b/core/res/res/color/text_color_secondary_device_default_dark.xml
index 799636ad..f79fd62 100644
--- a/core/res/res/color/text_color_secondary_device_default_dark.xml
+++ b/core/res/res/color/text_color_secondary_device_default_dark.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_200"/>
- <item android:color="@color/system_primary_200"/>
+ android:color="@color/system_neutral2_200"/>
+ <item android:color="@color/system_neutral2_200"/>
</selector>
diff --git a/core/res/res/color/text_color_secondary_device_default_light.xml b/core/res/res/color/text_color_secondary_device_default_light.xml
index 4793bb8..c58f565 100644
--- a/core/res/res/color/text_color_secondary_device_default_light.xml
+++ b/core/res/res/color/text_color_secondary_device_default_light.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_700"/>
- <item android:color="@color/system_primary_700"/>
+ android:color="@color/system_neutral2_700"/>
+ <item android:color="@color/system_neutral2_700"/>
</selector>
diff --git a/core/res/res/color/text_color_tertiary_device_default_dark.xml b/core/res/res/color/text_color_tertiary_device_default_dark.xml
index c828631..63fdc81 100644
--- a/core/res/res/color/text_color_tertiary_device_default_dark.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_dark.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_400"/>
- <item android:color="@color/system_primary_400"/>
+ android:color="@color/system_neutral2_400"/>
+ <item android:color="@color/system_neutral2_400"/>
</selector>
diff --git a/core/res/res/color/text_color_tertiary_device_default_light.xml b/core/res/res/color/text_color_tertiary_device_default_light.xml
index 82c420a..1ad6f6a 100644
--- a/core/res/res/color/text_color_tertiary_device_default_light.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_light.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_500"/>
- <item android:color="@color/system_primary_500"/>
+ android:color="@color/system_neutral2_500"/>
+ <item android:color="@color/system_neutral2_500"/>
</selector>
diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index dd79a0b..5f1b60e 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -24,7 +24,6 @@
android:paddingTop="8dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
- android:layout_marginEnd="2dp"
android:gravity="center"
android:background="@drawable/notification_material_media_action_background"
android:visibility="gone"
diff --git a/core/res/res/layout/notification_material_media_seekbar.xml b/core/res/res/layout/notification_material_media_seekbar.xml
deleted file mode 100644
index 4aa8acc..0000000
--- a/core/res/res/layout/notification_material_media_seekbar.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/notification_media_progress"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_alignParentBottom="true"
- >
- <SeekBar android:id="@+id/notification_media_progress_bar"
- style="@style/Widget.ProgressBar.Horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxHeight="3dp"
- android:paddingTop="24dp"
- android:paddingBottom="24dp"
- android:clickable="true"
- android:layout_marginBottom="-24dp"
- android:layout_marginTop="-12dp"
- android:splitTrack="false"
- />
- <FrameLayout
- android:id="@+id/notification_media_progress_time"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginBottom="11dp"
- >
-
- <!-- width is set to "match_parent" to avoid extra layout calls -->
- <TextView android:id="@+id/notification_media_elapsed_time"
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:gravity="start"
- />
-
- <TextView android:id="@+id/notification_media_total_time"
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:gravity="end"
- />
- </FrameLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index bad9a6b..e644cd5 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -46,20 +46,6 @@
android:padding="@dimen/notification_icon_circle_padding"
/>
- <ImageView
- android:id="@+id/right_icon"
- android:layout_width="@dimen/notification_right_icon_size"
- android:layout_height="@dimen/notification_right_icon_size"
- android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginEnd="@dimen/notification_header_expand_icon_size"
- android:background="@drawable/notification_large_icon_outline"
- android:clipToOutline="true"
- android:importantForAccessibility="no"
- android:scaleType="centerCrop"
- />
-
<FrameLayout
android:id="@+id/alternate_expand_target"
android:layout_width="@dimen/notification_content_margin_start"
@@ -68,95 +54,116 @@
android:importantForAccessibility="no"
/>
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="end">
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
- />
-
- </FrameLayout>
-
<LinearLayout
- android:id="@+id/notification_headerless_view_column"
+ android:id="@+id/notification_headerless_view_row"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
- android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
- android:orientation="vertical"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
>
- <!-- extends ViewGroup -->
- <NotificationTopLineView
- android:id="@+id/notification_top_line"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_headerless_line_height"
- android:layout_marginEnd="@dimen/notification_heading_margin_end"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:clipChildren="false"
- android:theme="@style/Theme.DeviceDefault.Notification"
- >
-
- <!--
- NOTE: The notification_top_line_views layout contains the app_name_text.
- In order to include the title view at the beginning, the Notification.Builder
- has logic to hide that view whenever this title view is to be visible.
- -->
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:singleLine="true"
- android:textAlignment="viewStart"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- />
-
- <include layout="@layout/notification_top_line_views" />
-
- </NotificationTopLineView>
-
<LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_heading_margin_end"
- android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
android:orientation="vertical"
>
- <com.android.internal.widget.NotificationVanishingFrameLayout
- android:layout_width="match_parent"
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
>
- <!-- This is the simplest way to keep this text vertically centered without using
- gravity="center_vertical" which causes jumpiness in expansion animations. -->
- <include
- layout="@layout/notification_template_text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="center_vertical"
- android:layout_marginTop="0dp"
- />
- </com.android.internal.widget.NotificationVanishingFrameLayout>
- <include
- layout="@layout/notification_template_progress"
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_headerless_line_height"
- />
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <com.android.internal.widget.NotificationVanishingFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ />
+
+ </LinearLayout>
</LinearLayout>
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
</LinearLayout>
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index aa20ad3..ff64315 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -20,17 +20,8 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="#00000000"
android:tag="bigMediaNarrow"
>
- <!-- The size will actually be determined at runtime -->
- <ImageView
- android:id="@+id/right_icon"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_gravity="top|end"
- android:scaleType="centerCrop"
- />
<include
layout="@layout/notification_template_header"
@@ -49,46 +40,27 @@
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="46dp"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginBottom="@dimen/notification_content_margin"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
>
- <!-- TODO(b/172652345): fix the media style -->
- <!--<include layout="@layout/notification_template_part_line1"/>-->
- <!--<include layout="@layout/notification_template_text"/>-->
-
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart"
- />
-
- <com.android.internal.widget.ImageFloatingTextView
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="top"
- android:layout_marginTop="0.5dp"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:gravity="top"
- android:singleLine="true"
- android:textAlignment="viewStart"
- />
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_text"/>
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-21dp"
+ android:paddingStart="44dp"
+ android:paddingEnd="44dp"
+ android:paddingBottom="@dimen/media_notification_actions_padding_bottom"
+ android:gravity="top"
android:orientation="horizontal"
android:layoutDirection="ltr"
- style="@style/NotificationMediaActionContainer"
>
<include
@@ -117,10 +89,8 @@
/>
</LinearLayout>
- <ViewStub
- android:id="@+id/notification_media_seekbar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
</LinearLayout>
+
+ <include layout="@layout/notification_template_right_icon" />
+
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 542e59d..2991b17 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -19,101 +19,173 @@
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#00000000"
+ android:layout_height="@dimen/notification_min_height"
android:tag="media"
>
- <ImageView android:id="@+id/right_icon"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:adjustViewBounds="true"
- android:layout_gravity="top|end"
+
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_left_icon_size"
+ android:layout_height="@dimen/notification_left_icon_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
android:scaleType="centerCrop"
- />
- <include layout="@layout/notification_template_header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/media_notification_header_height"
+ android:visibility="gone"
/>
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ />
+
<LinearLayout
+ android:id="@+id/notification_headerless_view_row"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:id="@+id/notification_media_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
>
+
<LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:layout_marginTop="46dp"
- android:layout_alignParentTop="true"
- android:tag="media"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:orientation="vertical"
>
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
<LinearLayout
- android:id="@+id/notification_content_container"
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="fill_vertical"
- android:layout_weight="1"
- android:paddingBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
- <!-- TODO(b/172652345): fix the media style -->
- <!--<include layout="@layout/notification_template_part_line1"/>-->
- <!--<include layout="@layout/notification_template_text"/>-->
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ <com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
/>
- <com.android.internal.widget.ImageFloatingTextView
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="top"
- android:layout_marginTop="0.5dp"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:gravity="top"
- android:singleLine="true"
- android:textAlignment="viewStart"
- />
</LinearLayout>
- <LinearLayout
- android:id="@+id/media_actions"
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layoutDirection="ltr"
+ android:orientation="horizontal"
+ >
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action0"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action1"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action2"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|end"
- android:layout_marginStart="10dp"
- android:layoutDirection="ltr"
- android:orientation="horizontal"
- >
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action0"
+ android:layout_gravity="center_vertical|end"
/>
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action1"
- />
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action2"
- />
- </LinearLayout>
- </LinearLayout>
- <ViewStub android:id="@+id/notification_media_seekbar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- />
+
+ </FrameLayout>
+
</LinearLayout>
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 22467e4..748e3b1 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -244,114 +244,188 @@
<color name="conversation_important_highlight">#F9AB00</color>
- <!-- Lightest shade of the primary color used by the system. White.
+ <!-- Lightest shade of the accent color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_0">#ffffff</color>
- <!-- Shade of the primary system color at 95% lightness.
+ <color name="system_accent1_0">#ffffff</color>
+ <!-- Shade of the accent system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_50">#f2f2f2</color>
- <!-- Shade of the primary system color at 90% lightness.
+ <color name="system_accent1_50">#9CFFF2</color>
+ <!-- Shade of the accent system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_100">#e3e3e3</color>
- <!-- Shade of the primary system color at 80% lightness.
+ <color name="system_accent1_100">#8DF5E3</color>
+ <!-- Shade of the accent system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_200">#c7c7c7</color>
- <!-- Shade of the primary system color at 70% lightness.
+ <color name="system_accent1_200">#71D8C7</color>
+ <!-- Shade of the accent system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_300">#ababab</color>
- <!-- Shade of the primary system color at 60% lightness.
+ <color name="system_accent1_300">#53BCAC</color>
+ <!-- Shade of the accent system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_400">#8f8f8f</color>
- <!-- Shade of the primary system color at 50% lightness.
+ <color name="system_accent1_400">#34A192</color>
+ <!-- Shade of the accent system color at 49% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_500">#757575</color>
- <!-- Shade of the primary system color at 40% lightness.
+ <color name="system_accent1_500">#008375</color>
+ <!-- Shade of the accent system color at 40% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_600">#5e5e5e</color>
- <!-- Shade of the primary system color at 30% lightness.
+ <color name="system_accent1_600">#006C5F</color>
+ <!-- Shade of the accent system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_700">#474747</color>
- <!-- Shade of the primary system color at 20% lightness.
+ <color name="system_accent1_700">#005747</color>
+ <!-- Shade of the accent system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_800">#303030</color>
- <!-- Shade of the primary system color at 10% lightness.
+ <color name="system_accent1_800">#003E31</color>
+ <!-- Shade of the accent system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_900">#1f1f1f</color>
- <!-- Darkest shade of the primary color used by the system. Black.
+ <color name="system_accent1_900">#002214</color>
+ <!-- Darkest shade of the accent color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_1000">#000000</color>
+ <color name="system_accent1_1000">#000000</color>
- <!-- Lightest shade of the secondary color used by the system. White.
+ <!-- Lightest shade of the secondary accent color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_0">#ffffff</color>
- <!-- Shade of the secondary system color at 95% lightness.
+ <color name="system_accent2_0">#ffffff</color>
+ <!-- Shade of the secondary accent system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_50">#91fff4</color>
- <!-- Shade of the secondary system color at 90% lightness.
+ <color name="system_accent2_50">#CDFAF1</color>
+ <!-- Shade of the secondary accent system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_100">#83f6e5</color>
- <!-- Shade of the secondary system color at 80% lightness.
+ <color name="system_accent2_100">#BFEBE3</color>
+ <!-- Shade of the secondary accent system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_200">#65d9c9</color>
- <!-- Shade of the secondary system color at 70% lightness.
+ <color name="system_accent2_200">#A4CFC7</color>
+ <!-- Shade of the secondary accent system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_300">#45bdae</color>
- <!-- Shade of the secondary system color at 60% lightness.
+ <color name="system_accent2_300">#89B4AC</color>
+ <!-- Shade of the secondary accent system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_400">#1fa293</color>
- <!-- Shade of the secondary system color at 50% lightness.
+ <color name="system_accent2_400">#6F9991</color>
+ <!-- Shade of the secondary accent system color at 49% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_500">#008377</color>
- <!-- Shade of the secondary system color at 40% lightness.
+ <color name="system_accent2_500">#537C75</color>
+ <!-- Shade of the secondary accent system color at 40% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_600">#006d61</color>
- <!-- Shade of the secondary system color at 30% lightness.
+ <color name="system_accent2_600">#3D665F</color>
+ <!-- Shade of the secondary accent system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_700">#005449</color>
- <!-- Shade of the secondary system color at 20% lightness.
+ <color name="system_accent2_700">#254E47</color>
+ <!-- Shade of the secondary accent system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_800">#003c33</color>
- <!-- Shade of the secondary system color at 10% lightness.
+ <color name="system_accent2_800">#0C3731</color>
+ <!-- Shade of the secondary accent system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_900">#00271e</color>
- <!-- Darkest shade of the secondary color used by the system. Black.
+ <color name="system_accent2_900">#00211C</color>
+ <!-- Darkest shade of the secondary accent color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_1000">#000000</color>
+ <color name="system_accent2_1000">#000000</color>
+
+ <!-- Lightest shade of the tertiary accent color used by the system. White.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_0">#ffffff</color>
+ <!-- Shade of the tertiary accent system color at 95% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_50">#F9EAFF</color>
+ <!-- Shade of the tertiary accent system color at 90% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_100">#ECDBFF</color>
+ <!-- Shade of the tertiary accent system color at 80% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_200">#CFBFEB</color>
+ <!-- Shade of the tertiary accent system color at 70% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_300">#B3A4CF</color>
+ <!-- Shade of the tertiary accent system color at 60% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_400">#988AB3</color>
+ <!-- Shade of the tertiary accent system color at 49% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_500">#7B6E96</color>
+ <!-- Shade of the tertiary accent system color at 40% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_600">#64587F</color>
+ <!-- Shade of the tertiary accent system color at 30% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_700">#4C4165</color>
+ <!-- Shade of the tertiary accent system color at 20% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_800">#352B4D</color>
+ <!-- Shade of the tertiary accent system color at 10% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_900">#1E1636</color>
+ <!-- Darkest shade of the tertiary accent color used by the system. Black.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_accent3_1000">#000000</color>
<!-- Lightest shade of the neutral color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_0">#ffffff</color>
+ <color name="system_neutral1_0">#ffffff</color>
<!-- Shade of the neutral system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_50">#f0f0f0</color>
+ <color name="system_neutral1_50">#f0f0f0</color>
<!-- Shade of the neutral system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_100">#e2e2e2</color>
+ <color name="system_neutral1_100">#e2e2e2</color>
<!-- Shade of the neutral system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_200">#c6c6c6</color>
+ <color name="system_neutral1_200">#c6c6c6</color>
<!-- Shade of the neutral system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_300">#ababab</color>
+ <color name="system_neutral1_300">#ababab</color>
<!-- Shade of the neutral system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_400">#909090</color>
- <!-- Shade of the neutral system color at 50% lightness.
+ <color name="system_neutral1_400">#909090</color>
+ <!-- Shade of the neutral system color at 49% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_500">#757575</color>
+ <color name="system_neutral1_500">#757575</color>
<!-- Shade of the neutral system color at 40% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_600">#5e5e5e</color>
+ <color name="system_neutral1_600">#5e5e5e</color>
<!-- Shade of the neutral system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_700">#464646</color>
+ <color name="system_neutral1_700">#464646</color>
<!-- Shade of the neutral system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_800">#303030</color>
+ <color name="system_neutral1_800">#303030</color>
<!-- Shade of the neutral system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_900">#1b1b1b</color>
+ <color name="system_neutral1_900">#1b1b1b</color>
<!-- Darkest shade of the neutral color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_1000">#000000</color>
+ <color name="system_neutral1_1000">#000000</color>
+
+ <!-- Lightest shade of the secondary neutral color used by the system. White.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_0">#ffffff</color>
+ <!-- Shade of the secondary neutral system color at 95% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_50">#f0f0f0</color>
+ <!-- Shade of the secondary neutral system color at 90% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_100">#e2e2e2</color>
+ <!-- Shade of the secondary neutral system color at 80% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_200">#c6c6c6</color>
+ <!-- Shade of the secondary neutral system color at 70% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_300">#ababab</color>
+ <!-- Shade of the secondary neutral system color at 60% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_400">#909090</color>
+ <!-- Shade of the secondary neutral system color at 49% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_500">#757575</color>
+ <!-- Shade of the secondary neutral system color at 40% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_600">#5e5e5e</color>
+ <!-- Shade of the secondary neutral system color at 30% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_700">#464646</color>
+ <!-- Shade of the secondary neutral system color at 20% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_800">#303030</color>
+ <!-- Shade of the secondary neutral system color at 10% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_900">#1b1b1b</color>
+ <!-- Darkest shade of the secondary neutral color used by the system. Black.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_1000">#000000</color>
</resources>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 9b56321..3fbd7ca 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -17,9 +17,9 @@
<!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
overlaying new theme colors. -->
<resources>
- <color name="primary_device_default_dark">@color/system_primary_800</color>
- <color name="primary_device_default_light">@color/system_primary_50</color>
- <color name="primary_device_default_settings">@color/system_primary_800</color>
+ <color name="primary_device_default_dark">@color/system_neutral1_800</color>
+ <color name="primary_device_default_light">@color/system_neutral1_50</color>
+ <color name="primary_device_default_settings">@color/system_neutral1_800</color>
<color name="primary_device_default_settings_light">@color/primary_device_default_light</color>
<color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color>
<color name="primary_dark_device_default_light">@color/primary_device_default_light</color>
@@ -33,14 +33,14 @@
<color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
<color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
- <color name="accent_device_default_light">@color/system_secondary_600</color>
- <color name="accent_device_default_dark">@color/system_secondary_200</color>
+ <color name="accent_device_default_light">@color/system_accent1_600</color>
+ <color name="accent_device_default_dark">@color/system_accent1_200</color>
<color name="accent_device_default">@color/accent_device_default_light</color>
- <color name="background_device_default_dark">@color/system_primary_800</color>
- <color name="background_device_default_light">@color/system_primary_50</color>
- <color name="background_floating_device_default_dark">@color/system_primary_900</color>
- <color name="background_floating_device_default_light">@color/system_primary_100</color>
+ <color name="background_device_default_dark">@color/system_neutral1_800</color>
+ <color name="background_device_default_light">@color/system_neutral1_50</color>
+ <color name="background_floating_device_default_dark">@color/system_neutral1_900</color>
+ <color name="background_floating_device_default_light">@color/system_neutral1_100</color>
<!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors-->
<color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color>
@@ -50,8 +50,8 @@
<color name="error_color_device_default_dark">@color/error_color_material_dark</color>
<color name="error_color_device_default_light">@color/error_color_material_light</color>
- <color name="list_divider_color_light">@color/system_primary_500</color>
- <color name="list_divider_color_dark">@color/system_primary_400</color>
+ <color name="list_divider_color_light">@color/system_neutral1_200</color>
+ <color name="list_divider_color_dark">@color/system_neutral1_700</color>
<color name="list_divider_opacity_device_default_light">@android:color/white</color>
<color name="list_divider_opacity_device_default_dark">@android:color/white</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b4ef63f..a3b3bb5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -951,6 +951,11 @@
-->
<integer name="config_longPressOnPowerBehavior">1</integer>
+ <!-- Whether the setting to change long press on power behaviour from default to assistant (5)
+ is available in Settings.
+ -->
+ <bool name="config_longPressOnPowerForAssistantSettingAvailable">true</bool>
+
<!-- Control the behavior when the user long presses the power button for a long time.
0 - Nothing
1 - Global actions menu
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 43dbd38..afbbe46 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -226,9 +226,6 @@
<!-- The margin on the end of the top-line content views (accommodates the expander) -->
<dimen name="notification_heading_margin_end">56dp</dimen>
- <!-- The margin for text at the end of the image view for media notifications -->
- <dimen name="notification_media_image_margin_end">72dp</dimen>
-
<!-- The height of the notification action list -->
<dimen name="notification_action_list_height">60dp</dimen>
@@ -345,6 +342,9 @@
<!-- The minimum width of the app name in the header if it shrinks -->
<dimen name="notification_header_shrink_min_width">72dp</dimen>
+ <!-- The minimum width of optional header fields below which the view is simply hidden -->
+ <dimen name="notification_header_shrink_hide_width">24sp</dimen>
+
<!-- The size of the media actions in the media notification. -->
<dimen name="media_notification_action_button_size">48dp</dimen>
@@ -360,9 +360,6 @@
<!-- The absolute height for the header in a media notification. -->
<dimen name="media_notification_header_height">@dimen/notification_header_height</dimen>
- <!-- The margin of the content to an image-->
- <dimen name="notification_content_image_margin_end">8dp</dimen>
-
<!-- The padding at the end of actions when the snooze and bubble buttons are gone-->
<dimen name="snooze_and_bubble_gone_padding_end">12dp</dimen>
@@ -483,9 +480,6 @@
<!-- Top padding for notification when text is large and narrow (i.e. it has 3 lines -->
<dimen name="notification_top_pad_large_text_narrow">-4dp</dimen>
- <!-- Padding for notification icon when drawn with circle around it -->
- <dimen name="notification_large_icon_circle_padding">11dp</dimen>
-
<!-- The margin on top of the text of the notification -->
<dimen name="notification_text_margin_top">6dp</dimen>
@@ -736,12 +730,10 @@
<dimen name="notification_big_picture_max_height">284dp</dimen>
<!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. This value is determined by the standard panel size -->
<dimen name="notification_big_picture_max_width">416dp</dimen>
- <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. This value is determined by the expanded media template-->
- <dimen name="notification_media_image_max_height">140dp</dimen>
- <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
- <dimen name="notification_media_image_max_width">280dp</dimen>
<!-- The size of the right icon -->
<dimen name="notification_right_icon_size">48dp</dimen>
+ <!-- The margin between the right icon and the content. -->
+ <dimen name="notification_right_icon_content_margin">12dp</dimen>
<!-- The top and bottom margin of the right icon in the normal notification states -->
<dimen name="notification_right_icon_headerless_margin">20dp</dimen>
<!-- The top margin of the right icon in the "big" notification states -->
@@ -762,10 +754,6 @@
<dimen name="notification_big_picture_max_height_low_ram">208dp</dimen>
<!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. -->
<dimen name="notification_big_picture_max_width_low_ram">294dp</dimen>
- <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. -->
- <dimen name="notification_media_image_max_height_low_ram">100dp</dimen>
- <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
- <dimen name="notification_media_image_max_width_low_ram">100dp</dimen>
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen>
<!-- The maximum size of the grayscale icon -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3e5fad8..7694faf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3102,45 +3102,68 @@
<!-- color definitions go here -->
<!-- Material design dynamic system palette:-->
- <!-- Primary color -->
- <public name="system_primary_0" />
- <public name="system_primary_50" />
- <public name="system_primary_100" />
- <public name="system_primary_200" />
- <public name="system_primary_300" />
- <public name="system_primary_400" />
- <public name="system_primary_500" />
- <public name="system_primary_600" />
- <public name="system_primary_700" />
- <public name="system_primary_800" />
- <public name="system_primary_900" />
- <public name="system_primary_1000" />
- <!-- Secondary color -->
- <public name="system_secondary_0" />
- <public name="system_secondary_50" />
- <public name="system_secondary_100" />
- <public name="system_secondary_200" />
- <public name="system_secondary_300" />
- <public name="system_secondary_400" />
- <public name="system_secondary_500" />
- <public name="system_secondary_600" />
- <public name="system_secondary_700" />
- <public name="system_secondary_800" />
- <public name="system_secondary_900" />
- <public name="system_secondary_1000" />
- <!-- Neutral color -->
- <public name="system_neutral_0" />
- <public name="system_neutral_50" />
- <public name="system_neutral_100" />
- <public name="system_neutral_200" />
- <public name="system_neutral_300" />
- <public name="system_neutral_400" />
- <public name="system_neutral_500" />
- <public name="system_neutral_600" />
- <public name="system_neutral_700" />
- <public name="system_neutral_800" />
- <public name="system_neutral_900" />
- <public name="system_neutral_1000" />
+ <!-- Neutral colors for background and text -->
+ <public name="system_neutral1_0" />
+ <public name="system_neutral1_50" />
+ <public name="system_neutral1_100" />
+ <public name="system_neutral1_200" />
+ <public name="system_neutral1_300" />
+ <public name="system_neutral1_400" />
+ <public name="system_neutral1_500" />
+ <public name="system_neutral1_600" />
+ <public name="system_neutral1_700" />
+ <public name="system_neutral1_800" />
+ <public name="system_neutral1_900" />
+ <public name="system_neutral1_1000" />
+ <public name="system_neutral2_0" />
+ <public name="system_neutral2_50" />
+ <public name="system_neutral2_100" />
+ <public name="system_neutral2_200" />
+ <public name="system_neutral2_300" />
+ <public name="system_neutral2_400" />
+ <public name="system_neutral2_500" />
+ <public name="system_neutral2_600" />
+ <public name="system_neutral2_700" />
+ <public name="system_neutral2_800" />
+ <public name="system_neutral2_900" />
+ <public name="system_neutral2_1000" />
+ <!-- Accent colors, for buttons and UI decorations -->
+ <public name="system_accent1_0" />
+ <public name="system_accent1_50" />
+ <public name="system_accent1_100" />
+ <public name="system_accent1_200" />
+ <public name="system_accent1_300" />
+ <public name="system_accent1_400" />
+ <public name="system_accent1_500" />
+ <public name="system_accent1_600" />
+ <public name="system_accent1_700" />
+ <public name="system_accent1_800" />
+ <public name="system_accent1_900" />
+ <public name="system_accent1_1000" />
+ <public name="system_accent2_0" />
+ <public name="system_accent2_50" />
+ <public name="system_accent2_100" />
+ <public name="system_accent2_200" />
+ <public name="system_accent2_300" />
+ <public name="system_accent2_400" />
+ <public name="system_accent2_500" />
+ <public name="system_accent2_600" />
+ <public name="system_accent2_700" />
+ <public name="system_accent2_800" />
+ <public name="system_accent2_900" />
+ <public name="system_accent2_1000" />
+ <public name="system_accent3_0" />
+ <public name="system_accent3_50" />
+ <public name="system_accent3_100" />
+ <public name="system_accent3_200" />
+ <public name="system_accent3_300" />
+ <public name="system_accent3_400" />
+ <public name="system_accent3_500" />
+ <public name="system_accent3_600" />
+ <public name="system_accent3_700" />
+ <public name="system_accent3_800" />
+ <public name="system_accent3_900" />
+ <public name="system_accent3_1000" />
</public-group>
<public-group type="dimen" first-id="0x01050008">
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c7ded0c..fbf67e0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1481,17 +1481,6 @@
<item name="android:windowExitAnimation">@anim/slide_out_down</item>
</style>
- <!-- The style for the container of media actions in a notification. -->
- <!-- @hide -->
- <style name="NotificationMediaActionContainer">
- <item name="layout_width">wrap_content</item>
- <item name="layout_height">wrap_content</item>
- <item name="layout_marginTop">-21dp</item>
- <item name="paddingStart">8dp</item>
- <item name="paddingBottom">@dimen/media_notification_actions_padding_bottom</item>
- <item name="gravity">top</item>
- </style>
-
<!-- The style for normal action button on notification -->
<style name="NotificationAction" parent="Widget.Material.Light.Button.Borderless.Small">
<item name="textColor">@color/notification_action_button_text_color</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1d74d85..d6a6f4d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -200,12 +200,7 @@
<java-symbol type="id" name="action2" />
<java-symbol type="id" name="action3" />
<java-symbol type="id" name="action4" />
- <java-symbol type="id" name="notification_media_seekbar_container" />
<java-symbol type="id" name="notification_media_content" />
- <java-symbol type="id" name="notification_media_progress" />
- <java-symbol type="id" name="notification_media_progress_bar" />
- <java-symbol type="id" name="notification_media_elapsed_time" />
- <java-symbol type="id" name="notification_media_total_time" />
<java-symbol type="id" name="big_picture" />
<java-symbol type="id" name="big_text" />
<java-symbol type="id" name="chronometer" />
@@ -525,7 +520,6 @@
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
<java-symbol type="dimen" name="notification_top_pad_large_text_narrow" />
- <java-symbol type="dimen" name="notification_large_icon_circle_padding" />
<java-symbol type="dimen" name="notification_badge_size" />
<java-symbol type="dimen" name="immersive_mode_cling_width" />
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
@@ -1564,7 +1558,6 @@
<java-symbol type="layout" name="immersive_mode_cling" />
<java-symbol type="layout" name="user_switching_dialog" />
<java-symbol type="layout" name="common_tab_settings" />
- <java-symbol type="layout" name="notification_material_media_seekbar" />
<java-symbol type="layout" name="resolver_list_per_profile" />
<java-symbol type="layout" name="chooser_list_per_profile" />
<java-symbol type="layout" name="resolver_empty_states" />
@@ -2921,6 +2914,7 @@
<java-symbol type="drawable" name="ic_expand_bundle" />
<java-symbol type="drawable" name="ic_collapse_bundle" />
<java-symbol type="dimen" name="notification_header_shrink_min_width" />
+ <java-symbol type="dimen" name="notification_header_shrink_hide_width" />
<java-symbol type="dimen" name="notification_content_margin_start" />
<java-symbol type="dimen" name="notification_content_margin_end" />
<java-symbol type="dimen" name="notification_heading_margin_end" />
@@ -3010,7 +3004,6 @@
<java-symbol type="string" name="new_sms_notification_content" />
<java-symbol type="dimen" name="media_notification_expanded_image_margin_bottom" />
- <java-symbol type="dimen" name="notification_content_image_margin_end" />
<java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
@@ -3019,8 +3012,6 @@
<java-symbol type="id" name="aerr_wait" />
- <java-symbol type="id" name="notification_content_container" />
-
<java-symbol type="plurals" name="duration_minutes_shortest" />
<java-symbol type="plurals" name="duration_hours_shortest" />
<java-symbol type="plurals" name="duration_days_shortest" />
@@ -3138,7 +3129,6 @@
<java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
- <java-symbol type="dimen" name="notification_media_image_margin_end" />
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_action_disabled_alpha" />
<java-symbol type="id" name="tag_margin_end_when_icon_visible" />
@@ -3461,17 +3451,14 @@
<java-symbol type="dimen" name="notification_big_picture_max_height"/>
<java-symbol type="dimen" name="notification_big_picture_max_width"/>
- <java-symbol type="dimen" name="notification_media_image_max_width"/>
- <java-symbol type="dimen" name="notification_media_image_max_height"/>
<java-symbol type="dimen" name="notification_right_icon_size"/>
+ <java-symbol type="dimen" name="notification_right_icon_content_margin"/>
<java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
<java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
<java-symbol type="dimen" name="notification_big_picture_max_width_low_ram"/>
- <java-symbol type="dimen" name="notification_media_image_max_width_low_ram"/>
- <java-symbol type="dimen" name="notification_media_image_max_height_low_ram"/>
<java-symbol type="dimen" name="notification_right_icon_size_low_ram"/>
<java-symbol type="dimen" name="notification_grayscale_icon_max_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height_low_ram"/>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 252938a..0ea6364 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,8 +16,6 @@
package android.app;
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -99,23 +97,6 @@
}
@Test
- public void testColorSatisfiedWhenBgDarkTextDarker() {
- Notification.Builder builder = getMediaNotification();
- Notification n = builder.build();
-
- assertTrue(n.isColorized());
-
- // An initial guess where the foreground color is actually darker than an already dark bg
- int backgroundColor = 0xff585868;
- int initialForegroundColor = 0xff505868;
- builder.setColorPalette(backgroundColor, initialForegroundColor);
- int primaryTextColor = builder.getPrimaryTextColor(builder.mParams);
- assertTrue(satisfiesTextContrast(primaryTextColor, backgroundColor));
- int secondaryTextColor = builder.getSecondaryTextColor(builder.mParams);
- assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor));
- }
-
- @Test
public void testHasCompletedProgress_noProgress() {
Notification n = new Notification.Builder(mContext).build();
diff --git a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
new file mode 100644
index 0000000..09985a8
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.content;
+
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ComponentCallbacksControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ComponentCallbacksControllerTest {
+ private ComponentCallbacksController mController;
+
+ @Before
+ public void setUp() {
+ mController = new ComponentCallbacksController();
+ }
+
+ @Test
+ public void testUnregisterCallbackWithoutRegistrationNoCrash() {
+ mController.unregisterCallbacks(new FakeComponentCallbacks());
+ }
+
+ @Test
+ public void testDispatchWithEmptyCallbacksNoCrash() {
+ mController.dispatchConfigurationChanged(new Configuration());
+ mController.dispatchLowMemory();
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+ }
+
+ @Test
+ public void testClearCallbacksNoCrash() {
+ mController.clearCallbacks();
+ }
+
+ @Test
+ public void testDispatchTrimMemoryWithoutComponentCallbacks2NoCrash() {
+ // Register a ComponentCallbacks instead of ComponentCallbacks2
+ mController.registerCallbacks(new FakeComponentCallbacks());
+
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+ }
+
+ @Test
+ public void testDispatchConfigurationChanged() throws Exception {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+ mController.dispatchConfigurationChanged(config);
+
+ assertThat(callback.mConfiguration).isEqualTo(config);
+
+ mController.dispatchConfigurationChanged(Configuration.EMPTY);
+
+ assertThat(callback.mConfiguration).isEqualTo(Configuration.EMPTY);
+ }
+
+ @Test
+ public void testDispatchLowMemory() {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ mController.dispatchLowMemory();
+
+ assertThat(callback.mLowMemoryCalled).isTrue();
+ }
+
+ @Test
+ public void testDispatchTrimMemory() {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+
+ assertThat(callback.mLevel).isEqualTo(TRIM_MEMORY_BACKGROUND);
+ }
+
+ private static class FakeComponentCallbacks implements ComponentCallbacks {
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {}
+
+ @Override
+ public void onLowMemory() {}
+ }
+
+ private static class TestComponentCallbacks2 implements ComponentCallbacks2 {
+ private Configuration mConfiguration;
+ private boolean mLowMemoryCalled;
+ private int mLevel;
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mConfiguration = newConfig;
+ }
+
+ @Override
+ public void onLowMemory() {
+ mLowMemoryCalled = true;
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mLevel = level;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 11239db..30b2d8e 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -28,13 +28,15 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.Arrays;
@Presubmit
@RunWith(JUnit4.class)
public class CombinedVibrationEffectTest {
private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
- private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
+ private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.Composed(
+ new ArrayList<>(), 0);
@Test
public void testValidateMono() {
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index d555cd9..242adab 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -18,13 +18,9 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-import static org.junit.Assert.assertArrayEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -35,11 +31,13 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
import android.platform.test.annotations.Presubmit;
import com.android.internal.R;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@@ -53,9 +51,6 @@
private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3";
private static final String UNKNOWN_URI = "content://test/system/other_audio";
- private static final float INTENSITY_SCALE_TOLERANCE = 1e-2f;
- private static final int AMPLITUDE_SCALE_TOLERANCE = 1;
-
private static final long TEST_TIMING = 100;
private static final int TEST_AMPLITUDE = 100;
private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
@@ -68,12 +63,6 @@
VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
private static final VibrationEffect TEST_WAVEFORM =
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
- private static final VibrationEffect TEST_COMPOSED =
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0f, 100)
- .compose();
@Test
public void getRingtones_noPrebakedRingtones() {
@@ -169,250 +158,106 @@
}
@Test
- public void testScalePrebaked_scalesFallbackEffect() {
- VibrationEffect.Prebaked prebaked =
- (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]);
- assertSame(prebaked, prebaked.scale(0.5f));
-
- prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM, TEST_ONE_SHOT);
- VibrationEffect.OneShot scaledFallback =
- (VibrationEffect.OneShot) prebaked.scale(0.5f).getFallbackEffect();
- assertEquals(34, scaledFallback.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
- }
-
- @Test
- public void testResolvePrebaked_resolvesFallbackEffectIfSet() {
- VibrationEffect.Prebaked prebaked =
- (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]);
- assertSame(prebaked, prebaked.resolve(1000));
-
- prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM,
- VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE));
- VibrationEffect.OneShot resolvedFallback =
- (VibrationEffect.OneShot) prebaked.resolve(10).getFallbackEffect();
- assertEquals(10, resolvedFallback.getAmplitude());
- }
-
- @Test
- public void testScaleOneShot() {
- VibrationEffect.OneShot unset = new VibrationEffect.OneShot(
- TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
- assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, unset.scale(2).getAmplitude());
-
- VibrationEffect.OneShot initial = (VibrationEffect.OneShot) TEST_ONE_SHOT;
-
- VibrationEffect.OneShot halved = initial.scale(0.5f);
- assertEquals(34, halved.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
- VibrationEffect.OneShot copied = initial.scale(1f);
- assertEquals(TEST_AMPLITUDE, copied.getAmplitude());
-
- VibrationEffect.OneShot scaledUp = initial.scale(1.5f);
- assertTrue(scaledUp.getAmplitude() > initial.getAmplitude());
- VibrationEffect.OneShot restored = scaledUp.scale(2 / 3f);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(105, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
- VibrationEffect.OneShot scaledDown = initial.scale(0.8f);
- assertTrue(scaledDown.getAmplitude() < initial.getAmplitude());
- restored = scaledDown.scale(1.25f);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(101, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
- // Does not go below min amplitude while scaling down.
- VibrationEffect.OneShot minAmplitude = new VibrationEffect.OneShot(TEST_TIMING, 1);
- assertEquals(1, minAmplitude.scale(0.5f).getAmplitude());
- }
-
- @Test
public void testResolveOneShot() {
- VibrationEffect.OneShot initial = (VibrationEffect.OneShot) DEFAULT_ONE_SHOT;
- VibrationEffect.OneShot resolved = initial.resolve(239);
- assertNotSame(initial, resolved);
- assertEquals(239, resolved.getAmplitude());
+ VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
+ assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
- // Ignores input when amplitude already set.
- VibrationEffect.OneShot resolved2 = resolved.resolve(10);
- assertSame(resolved, resolved2);
- assertEquals(239, resolved2.getAmplitude());
- }
-
- @Test
- public void testResolveOneshotFailsWhenMaxAmplitudeAboveThreshold() {
- try {
- TEST_ONE_SHOT.resolve(1000);
- fail("Max amplitude above threshold, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testResolveOneshotFailsWhenAmplitudeNonPositive() {
- try {
- TEST_ONE_SHOT.resolve(0);
- fail("Amplitude is set to zero, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testScaleWaveform() {
- VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM;
-
- VibrationEffect.Waveform copied = initial.scale(1f);
- assertArrayEquals(TEST_AMPLITUDES, copied.getAmplitudes());
-
- VibrationEffect.Waveform scaled = initial.scale(0.9f);
- assertEquals(216, scaled.getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(0, scaled.getAmplitudes()[1]);
- assertEquals(-1, scaled.getAmplitudes()[2]);
-
- VibrationEffect.Waveform minAmplitude = new VibrationEffect.Waveform(
- new long[]{100}, new int[] {1}, -1);
- assertArrayEquals(new int[]{1}, minAmplitude.scale(0.5f).getAmplitudes());
+ assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
}
@Test
public void testResolveWaveform() {
- VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM;
- VibrationEffect.Waveform resolved = initial.resolve(123);
- assertNotSame(initial, resolved);
- assertArrayEquals(new int[]{255, 0, 123}, resolved.getAmplitudes());
+ VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
+ assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
- // Ignores input when amplitude already set.
- VibrationEffect.Waveform resolved2 = resolved.resolve(10);
- assertSame(resolved, resolved2);
- assertArrayEquals(new int[]{255, 0, 123}, resolved2.getAmplitudes());
+ assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
}
@Test
- public void testResolveWaveformFailsWhenMaxAmplitudeAboveThreshold() {
- try {
- TEST_WAVEFORM.resolve(1000);
- fail("Max amplitude above threshold, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
+ public void testResolvePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ assertEquals(effect, effect.resolve(51));
+ }
+
+ @Test
+ public void testResolveComposed() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
+ .compose();
+ assertEquals(effect, effect.resolve(51));
+ }
+
+ @Test
+ public void testApplyEffectStrengthOneShot() {
+ VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength(
+ VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(DEFAULT_ONE_SHOT, applied);
+ }
+
+ @Test
+ public void testApplyEffectStrengthWaveform() {
+ VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength(
+ VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(TEST_WAVEFORM, applied);
+ }
+
+ @Test
+ public void testApplyEffectStrengthPrebaked() {
+ VibrationEffect.Composed applied = VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
+ .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
+ ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength());
+ }
+
+ @Test
+ public void testApplyEffectStrengthComposed() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+ .compose();
+ assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT));
+ }
+
+ @Test
+ public void testScaleOneShot() {
+ VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
+ assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+
+ VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
+ assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScaleWaveform() {
+ VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
+ assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+
+ VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
+ assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScalePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ assertEquals(effect, scaledUp);
+
+ VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ assertEquals(effect, scaledDown);
}
@Test
public void testScaleComposed() {
- VibrationEffect.Composed initial = (VibrationEffect.Composed) TEST_COMPOSED;
-
- VibrationEffect.Composed copied = initial.scale(1);
- assertEquals(1f, copied.getPrimitiveEffects().get(0).scale);
- assertEquals(0.5f, copied.getPrimitiveEffects().get(1).scale);
- assertEquals(0f, copied.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed halved = initial.scale(0.5f);
- assertEquals(0.34f, halved.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0.17f, halved.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0f, halved.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed scaledUp = initial.scale(1.5f);
- // Does not scale up from 1.
- assertEquals(1f, scaledUp.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- assertTrue(0.5f < scaledUp.getPrimitiveEffects().get(1).scale);
- assertEquals(0f, scaledUp.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed restored = scaledUp.scale(2 / 3f);
- // The original value was not scaled up, so this only scales it down.
- assertEquals(0.53f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(0.47f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0f, restored.getPrimitiveEffects().get(2).scale);
-
- VibrationEffect.Composed scaledDown = initial.scale(0.8f);
- assertTrue(1f > scaledDown.getPrimitiveEffects().get(0).scale);
- assertTrue(0.5f > scaledDown.getPrimitiveEffects().get(1).scale);
- assertEquals(0f, scaledDown.getPrimitiveEffects().get(2).scale);
-
- restored = scaledDown.scale(1.25f);
- // Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(0.84f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0.5f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
- assertEquals(0f, restored.getPrimitiveEffects().get(2).scale);
- }
-
- @Test
- public void testResolveComposed_ignoresDefaultAmplitudeAndReturnsSameEffect() {
- VibrationEffect initial = TEST_COMPOSED;
- assertSame(initial, initial.resolve(1000));
- }
-
- @Test
- public void testScaleAppliesSameAdjustmentsOnAllEffects() {
- VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(TEST_TIMING, TEST_AMPLITUDE);
- VibrationEffect.Waveform waveform = new VibrationEffect.Waveform(
- new long[] { TEST_TIMING }, new int[]{ TEST_AMPLITUDE }, -1);
- VibrationEffect.Composed composed =
+ VibrationEffect.Composed effect =
(VibrationEffect.Composed) VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK,
- TEST_AMPLITUDE / 255f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
.compose();
- assertEquals(oneShot.scale(0.8f).getAmplitude(),
- waveform.scale(0.8f).getAmplitudes()[0],
- AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(oneShot.scale(1.2f).getAmplitude() / 255f,
- composed.scale(1.2f).getPrimitiveEffects().get(0).scale,
- INTENSITY_SCALE_TOLERANCE);
- }
+ VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
- @Test
- public void testScaleOnMaxAmplitude() {
- VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(
- TEST_TIMING, VibrationEffect.MAX_AMPLITUDE);
- VibrationEffect.Waveform waveform = new VibrationEffect.Waveform(
- new long[]{TEST_TIMING}, new int[]{VibrationEffect.MAX_AMPLITUDE}, -1);
- VibrationEffect.Composed composed =
- (VibrationEffect.Composed) VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
- .compose();
-
- // Scale up does NOT scale MAX_AMPLITUDE
- assertEquals(VibrationEffect.MAX_AMPLITUDE, oneShot.scale(1.1f).getAmplitude());
- assertEquals(VibrationEffect.MAX_AMPLITUDE, waveform.scale(1.2f).getAmplitudes()[0]);
- assertEquals(1f,
- composed.scale(1.4f).getPrimitiveEffects().get(0).scale,
- INTENSITY_SCALE_TOLERANCE); // This needs tolerance for float point comparison.
-
- // Scale down does scale MAX_AMPLITUDE
- assertEquals(216, oneShot.scale(0.9f).getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(180, waveform.scale(0.8f).getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE);
- assertEquals(0.57f, composed.scale(0.7f).getPrimitiveEffects().get(0).scale,
- INTENSITY_SCALE_TOLERANCE);
- }
-
- @Test
- public void getEffectStrength_returnsValueFromConstructor() {
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_LIGHT, null);
- Assert.assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, effect.getEffectStrength());
- }
-
- @Test
- public void getFallbackEffect_withFallbackDisabled_isNull() {
- VibrationEffect fallback = VibrationEffect.createOneShot(100, 100);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- false, VibrationEffect.EFFECT_STRENGTH_LIGHT);
- Assert.assertNull(effect.getFallbackEffect());
- }
-
- @Test
- public void getFallbackEffect_withoutEffectSet_isNull() {
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- true, VibrationEffect.EFFECT_STRENGTH_LIGHT);
- Assert.assertNull(effect.getFallbackEffect());
- }
-
- @Test
- public void getFallbackEffect_withFallback_returnsValueFromConstructor() {
- VibrationEffect fallback = VibrationEffect.createOneShot(100, 100);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_LIGHT, fallback);
- Assert.assertEquals(fallback, effect.getFallbackEffect());
+ VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
}
private Resources mockRingtoneResources() {
diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
new file mode 100644
index 0000000..de80812
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class PrebakedSegmentTest {
+
+ @Test
+ public void testCreation() {
+ PrebakedSegment prebaked = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+ assertEquals(-1, prebaked.getDuration());
+ assertTrue(prebaked.hasNonZeroAmplitude());
+ assertEquals(VibrationEffect.EFFECT_CLICK, prebaked.getEffectId());
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_MEDIUM, prebaked.getEffectStrength());
+ assertTrue(prebaked.shouldFallback());
+ }
+
+ @Test
+ public void testSerialization() {
+ PrebakedSegment original = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(original, PrebakedSegment.CREATOR.createFromParcel(parcel));
+ }
+
+ @Test
+ public void testValidate() {
+ new PrebakedSegment(VibrationEffect.EFFECT_CLICK, true,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrebakedSegment(1000, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrebakedSegment(VibrationEffect.EFFECT_TICK, false, 1000)
+ .validate());
+ }
+
+ @Test
+ public void testResolve_ignoresAndReturnsSameEffect() {
+ PrebakedSegment prebaked = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertSame(prebaked, prebaked.resolve(1000));
+ }
+
+ @Test
+ public void testApplyEffectStrength() {
+ PrebakedSegment medium = new PrebakedSegment(
+ VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+ PrebakedSegment light = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertNotEquals(medium, light);
+ assertEquals(medium.getEffectId(), light.getEffectId());
+ assertEquals(medium.shouldFallback(), light.shouldFallback());
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, light.getEffectStrength());
+
+ PrebakedSegment strong = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertNotEquals(medium, strong);
+ assertEquals(medium.getEffectId(), strong.getEffectId());
+ assertEquals(medium.shouldFallback(), strong.shouldFallback());
+ assertEquals(VibrationEffect.EFFECT_STRENGTH_STRONG, strong.getEffectStrength());
+
+ assertSame(medium, medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_MEDIUM));
+ // Invalid vibration effect strength is ignored.
+ assertSame(medium, medium.applyEffectStrength(1000));
+ }
+
+ @Test
+ public void testScale_ignoresAndReturnsSameEffect() {
+ PrebakedSegment prebaked = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertSame(prebaked, prebaked.scale(0.5f));
+ }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
new file mode 100644
index 0000000..538655b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class PrimitiveSegmentTest {
+ private static final float TOLERANCE = 1e-2f;
+
+ @Test
+ public void testCreation() {
+ PrimitiveSegment primitive = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
+
+ assertEquals(-1, primitive.getDuration());
+ assertTrue(primitive.hasNonZeroAmplitude());
+ assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId());
+ assertEquals(10, primitive.getDelay());
+ assertEquals(1f, primitive.getScale(), TOLERANCE);
+ }
+
+ @Test
+ public void testSerialization() {
+ PrimitiveSegment original = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(original, PrimitiveSegment.CREATOR.createFromParcel(parcel));
+ }
+
+ @Test
+ public void testValidate() {
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrimitiveSegment(1000, 0, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, -1, 0)
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, 1, -1)
+ .validate());
+ }
+
+ @Test
+ public void testResolve_ignoresAndReturnsSameEffect() {
+ PrimitiveSegment primitive = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+ assertSame(primitive, primitive.resolve(1000));
+ }
+
+ @Test
+ public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+ PrimitiveSegment primitive = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+ assertSame(primitive,
+ primitive.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ }
+
+ @Test
+ public void testScale_fullPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+ assertEquals(1f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0.34f, initial.scale(0.5f).getScale(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.71f, initial.scale(0.8f).getScale(), TOLERANCE);
+ assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_halfPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);
+
+ assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0.17f, initial.scale(0.5f).getScale(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.86f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.35f, initial.scale(0.8f).getScale(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_zeroPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
+
+ assertEquals(0f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.5f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
new file mode 100644
index 0000000..188b6c1
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class StepSegmentTest {
+ private static final float TOLERANCE = 1e-2f;
+
+ @Test
+ public void testCreation() {
+ StepSegment step = new StepSegment(/* amplitude= */ 1f, /* duration= */ 100);
+
+ assertEquals(100, step.getDuration());
+ assertTrue(step.hasNonZeroAmplitude());
+ assertEquals(1f, step.getAmplitude());
+ }
+
+ @Test
+ public void testSerialization() {
+ StepSegment original = new StepSegment(0.5f, 10);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ assertEquals(original, StepSegment.CREATOR.createFromParcel(parcel));
+ }
+
+ @Test
+ public void testValidate() {
+ new StepSegment(/* amplitude= */ 0f, /* duration= */ 100).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(/* amplitude= */ -2, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(/* amplitude= */ 2, 10).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> new StepSegment(2, /* duration= */ -1).validate());
+ }
+
+ @Test
+ public void testHasNonZeroAmplitude() {
+ assertTrue(new StepSegment(1f, 0).hasNonZeroAmplitude());
+ assertTrue(new StepSegment(0.01f, 0).hasNonZeroAmplitude());
+ assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0).hasNonZeroAmplitude());
+ assertFalse(new StepSegment(0, 0).hasNonZeroAmplitude());
+ }
+
+ @Test
+ public void testResolve() {
+ StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+ assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude());
+ assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE);
+
+ StepSegment resolved = new StepSegment(0, 0);
+ assertSame(resolved, resolved.resolve(100));
+
+ assertThrows(IllegalArgumentException.class, () -> resolved.resolve(1000));
+ }
+
+ @Test
+ public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+ StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+ assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+ }
+
+ @Test
+ public void testScale_fullAmplitude() {
+ StepSegment initial = new StepSegment(1f, 0);
+
+ assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0.34f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.71f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+ assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_halfAmplitude() {
+ StepSegment initial = new StepSegment(0.5f, 0);
+
+ assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0.17f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.86f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.35f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_zeroAmplitude() {
+ StepSegment initial = new StepSegment(0, 0);
+
+ assertEquals(0f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_defaultAmplitude() {
+ StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(0.5f).getAmplitude(),
+ TOLERANCE);
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(),
+ TOLERANCE);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
new file mode 100644
index 0000000..2dd3f69
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Internal tests for {@link SparseDoubleArray}.
+ *
+ * Run using:
+ * atest FrameworksCoreTests:android.util.SparseDoubleArrayTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseDoubleArrayTest {
+ private static final double EXACT_PRECISION = 0;
+ private static final double PRECISION = 0.000000001;
+
+ @Test
+ public void testPutGet() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+ assertEquals("Array should be empty", 0, sda.size());
+
+ final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+ final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+ Double.NaN,
+ 4311236312.0 / 3431470161413514334123.0,
+ 431123636434132151313412.0 / 34323.0,
+ 0};
+ for (int i = 0; i < keys.length; i++) {
+ sda.put(keys[i], values[i]);
+ }
+
+ assertEquals("Wrong size array", keys.length, sda.size());
+ // Due to the implementation, we actually expect EXACT double equality.
+ for (int i = 0; i < keys.length; i++) {
+ assertEquals("Wrong value at index " + i, values[i], sda.get(keys[i]), EXACT_PRECISION);
+ }
+
+ // Now check something that was never put in
+ assertEquals("Wrong value for absent index", 0, sda.get(100000), EXACT_PRECISION);
+ }
+
+ @Test
+ public void testAdd() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+
+ sda.put(4, 6.1);
+ sda.add(4, -1.2);
+ sda.add(2, -1.2);
+
+ assertEquals(6.1 - 1.2, sda.get(4), PRECISION);
+ assertEquals(-1.2, sda.get(2), PRECISION);
+ }
+
+ @Test
+ public void testKeyValueAt() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+
+ final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+ final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+ Double.NaN,
+ 4311236312.0 / 3431470161413514334123.0,
+ 431123636434132151313412.0 / 34323.0,
+ 0};
+ for (int i = 0; i < keys.length; i++) {
+ sda.put(keys[i], values[i]);
+ }
+
+ // Sort the sample data.
+ final ArrayMap<Integer, Double> map = new ArrayMap<>(keys.length);
+ for (int i = 0; i < keys.length; i++) {
+ map.put(keys[i], values[i]);
+ }
+ final int[] sortedKeys = Arrays.copyOf(keys, keys.length);
+ Arrays.sort(sortedKeys);
+
+ for (int i = 0; i < sortedKeys.length; i++) {
+ final int expectedKey = sortedKeys[i];
+ final double expectedValue = map.get(expectedKey);
+
+ assertEquals("Wrong key at index " + i, expectedKey, sda.keyAt(i), PRECISION);
+ assertEquals("Wrong value at index " + i, expectedValue, sda.valueAt(i), PRECISION);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 5de55d7..47556c3 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -98,12 +98,12 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// test if setVisibility can show IME
mImeConsumer.onWindowFocusGained();
- mImeConsumer.applyImeVisibility(true);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test if setVisibility can hide IME
- mImeConsumer.applyImeVisibility(false);
+ mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
@@ -117,7 +117,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained();
- mImeConsumer.applyImeVisibility(true /* setVisible */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */);
// set control and verify visibility is applied.
InsetsSourceControl control =
@@ -136,7 +136,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained();
- mImeConsumer.applyImeVisibility(true /* setVisible */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */);
// set control and verify visibility is applied.
InsetsSourceControl control = Mockito.spy(
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ff505c4..4390546 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -236,7 +236,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
// since there is no focused view, forcefully make IME visible.
- mController.applyImeVisibility(true /* setVisible */);
+ mController.show(Type.ime(), true /* fromIme */);
mController.show(Type.all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
@@ -244,7 +244,7 @@
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.applyImeVisibility(false /* setVisible */);
+ mController.hide(Type.ime(), true /* fromIme */);
mController.hide(Type.all());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
@@ -261,10 +261,10 @@
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
- mController.applyImeVisibility(true);
+ mController.show(Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.applyImeVisibility(false);
+ mController.hide(Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index d9012f64..4c58ad3 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -154,8 +154,8 @@
sOverrides.reset();
sOverrides.createPackageManager = mPackageManagerOverride;
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- Boolean.toString(false),
+ SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+ Boolean.toString(true),
true /* makeDefault*/);
}
@@ -1017,7 +1017,7 @@
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
- is(SHORTCUT_TARGET_SCORE_BOOST));
+ is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
}
@@ -1262,6 +1262,126 @@
.getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(0));
}
+ @Test
+ public void testShortcutTargetWithApplyAppLimits() throws InterruptedException {
+ // Set up resources
+ sOverrides.resources = Mockito.spy(
+ InstrumentationRegistry.getInstrumentation().getContext().getResources());
+ when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
+ .thenReturn(1);
+ Intent sendIntent = createSendTextIntent();
+ // We need app targets for direct targets to get displayed
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ // Create direct share target
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // Start activity
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+
+ // Insert the direct share target
+ Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+ List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
+ directShareToShortcutInfos.put(serviceTargets.get(0),
+ shortcutInfos.get(0).getShortcutInfo());
+ directShareToShortcutInfos.put(serviceTargets.get(1),
+ shortcutInfos.get(1).getShortcutInfo());
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activity.getAdapter().addServiceResults(
+ activity.createTestDisplayResolveInfo(sendIntent,
+ ri,
+ "testLabel",
+ "testInfo",
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
+ serviceTargets,
+ TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
+ directShareToShortcutInfos,
+ List.of())
+ );
+ // Thread.sleep shouldn't be a thing in an integration test but it's
+ // necessary here because of the way the code is structured
+ // TODO: restructure the tests b/129870719
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
+ activity.getAdapter().getCount(), is(3));
+ assertThat("Chooser should have exactly one selectable direct target",
+ activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+ assertThat("The resolver info must match the resolver info used to create the target",
+ activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+ assertThat("The display label must match",
+ activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
+ }
+
+ @Test
+ public void testShortcutTargetWithoutApplyAppLimits() throws InterruptedException {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+ Boolean.toString(false),
+ true /* makeDefault*/);
+ // Set up resources
+ sOverrides.resources = Mockito.spy(
+ InstrumentationRegistry.getInstrumentation().getContext().getResources());
+ when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
+ .thenReturn(1);
+ Intent sendIntent = createSendTextIntent();
+ // We need app targets for direct targets to get displayed
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ // Create direct share target
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // Start activity
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+
+ // Insert the direct share target
+ Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+ List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
+ directShareToShortcutInfos.put(serviceTargets.get(0),
+ shortcutInfos.get(0).getShortcutInfo());
+ directShareToShortcutInfos.put(serviceTargets.get(1),
+ shortcutInfos.get(1).getShortcutInfo());
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activity.getAdapter().addServiceResults(
+ activity.createTestDisplayResolveInfo(sendIntent,
+ ri,
+ "testLabel",
+ "testInfo",
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
+ serviceTargets,
+ TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
+ directShareToShortcutInfos,
+ List.of())
+ );
+ // Thread.sleep shouldn't be a thing in an integration test but it's
+ // necessary here because of the way the code is structured
+ // TODO: restructure the tests b/129870719
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ assertThat("Chooser should have 4 targets (2 apps, 2 direct)",
+ activity.getAdapter().getCount(), is(4));
+ assertThat("Chooser should have exactly two selectable direct target",
+ activity.getAdapter().getSelectableServiceTargetCount(), is(2));
+ assertThat("The resolver info must match the resolver info used to create the target",
+ activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+ assertThat("The display label must match",
+ activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
+ assertThat("The display label must match",
+ activity.getAdapter().getItem(1).getDisplayLabel(), is("testTitle1"));
+ }
+
// This test is too long and too slow and should not be taken as an example for future tests.
@Test
public void testDirectTargetLoggingWithAppTargetNotRankedPortrait()
diff --git a/data/etc/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml
index ac16af3..abde232 100644
--- a/data/etc/car/com.android.car.carlauncher.xml
+++ b/data/etc/car/com.android.car.carlauncher.xml
@@ -18,6 +18,7 @@
<privapp-permissions package="com.android.car.carlauncher">
<permission name="android.permission.ACTIVITY_EMBEDDING"/>
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 3aaf11c..4534d36 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -28,7 +28,7 @@
public long mNativeObject; // BLASTBufferQueue*
private static native long nativeCreate(String name, long surfaceControl, long width,
- long height, int format, boolean tripleBufferingEnabled);
+ long height, int format);
private static native void nativeDestroy(long ptr);
private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
private static native void nativeSetNextTransaction(long ptr, long transactionPtr);
@@ -53,9 +53,8 @@
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
- @PixelFormat.Format int format, boolean tripleBufferingEnabled) {
- mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format,
- tripleBufferingEnabled);
+ @PixelFormat.Format int format) {
+ mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
}
public void destroy() {
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index d2b3cf6..6bd0e0a 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.wm.shell">
+ <!-- System permission required by WM Shell Task Organizer. -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 4768cf5..fa94ec5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -126,7 +126,7 @@
*/
public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
return Settings.Secure.getInt(resolver,
- Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1;
+ Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0 /* Default OFF */) == 1;
}
void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 3f46fee..28c8f53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.startingsurface;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.app.ActivityThread;
@@ -31,6 +33,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
+import android.os.Trace;
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.SplashScreenView;
@@ -70,6 +73,7 @@
private int mIconNormalExitDistance;
private int mIconEarlyExitDistance;
private final TransactionPool mTransactionPool;
+ private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
@@ -109,49 +113,82 @@
return new ColorDrawable(getSystemBGColor());
}
- SplashScreenView makeSplashScreenContentView(Context context, int iconRes,
- int splashscreenContentResId) {
- updateDensity();
- // splash screen content will be deprecated after S.
- final SplashScreenView ssc =
- makeSplashscreenContentDrawable(context, splashscreenContentResId);
-
- if (ssc != null) {
- return ssc;
- }
-
- final SplashScreenWindowAttrs attrs =
- SplashScreenWindowAttrs.createWindowAttrs(context);
- final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
+ private @ColorInt int peekWindowBGColor(Context context) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
final Drawable themeBGDrawable;
-
- if (attrs.mWindowBgColor != 0) {
- themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
- } else if (attrs.mWindowBgResId != 0) {
- themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+ if (mTmpAttrs.mWindowBgColor != 0) {
+ themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
+ } else if (mTmpAttrs.mWindowBgResId != 0) {
+ themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
} else {
- Slog.w(TAG, "Window background not exist!");
themeBGDrawable = createDefaultBackgroundDrawable();
+ Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
}
+ final int estimatedWindowBGColor = estimateWindowBGColor(themeBGDrawable);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return estimatedWindowBGColor;
+ }
+
+ private int estimateWindowBGColor(Drawable themeBGDrawable) {
+ final DrawableColorTester themeBGTester =
+ new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
+ if (themeBGTester.nonTransparentRatio() == 0) {
+ // the window background is transparent, unable to draw
+ Slog.w(TAG, "Window background is transparent, fill background with black color");
+ return getSystemBGColor();
+ } else {
+ return themeBGTester.getDominateColor();
+ }
+ }
+
+ SplashScreenView makeSplashScreenContentView(Context context, int iconRes) {
+ updateDensity();
+
+ getWindowAttrs(context, mTmpAttrs);
+ final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final int animationDuration;
final Drawable iconDrawable;
- if (attrs.mReplaceIcon != null) {
- iconDrawable = attrs.mReplaceIcon;
+ if (mTmpAttrs.mReplaceIcon != null) {
+ iconDrawable = mTmpAttrs.mReplaceIcon;
animationDuration = Math.max(0,
- Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration));
+ Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
} else {
iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
: context.getPackageManager().getDefaultActivityIcon();
animationDuration = 0;
}
+ final int themeBGColor = peekWindowBGColor(context);
// TODO (b/173975965) Tracking the performance on improved splash screen.
return builder
.setContext(context)
- .setThemeDrawable(themeBGDrawable)
+ .setWindowBGColor(themeBGColor)
.setIconDrawable(iconDrawable)
.setIconAnimationDuration(animationDuration)
- .setBrandingDrawable(attrs.mBrandingImage)
- .setIconBackground(attrs.mIconBgColor).build();
+ .setBrandingDrawable(mTmpAttrs.mBrandingImage)
+ .setIconBackground(mTmpAttrs.mIconBgColor).build();
+ }
+
+ private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
+ final TypedArray typedArray = context.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ attrs.mWindowBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+ attrs.mReplaceIcon = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenAnimatedIcon);
+ attrs.mAnimationDuration = typedArray.getInt(
+ R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+ attrs.mBrandingImage = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenBrandingImage);
+ attrs.mIconBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
+ typedArray.recycle();
+ if (DEBUG) {
+ Slog.d(TAG, "window attributes color: "
+ + Integer.toHexString(attrs.mWindowBgColor)
+ + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+ + " brandImage " + attrs.mBrandingImage);
+ }
}
static class SplashScreenWindowAttrs {
@@ -161,35 +198,9 @@
private Drawable mBrandingImage = null;
private int mIconBgColor = Color.TRANSPARENT;
private int mAnimationDuration = 0;
-
- static SplashScreenWindowAttrs createWindowAttrs(Context context) {
- final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
- final TypedArray typedArray = context.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- attrs.mWindowBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
- attrs.mReplaceIcon = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenAnimatedIcon);
- attrs.mAnimationDuration = typedArray.getInt(
- R.styleable.Window_windowSplashScreenAnimationDuration, 0);
- attrs.mBrandingImage = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenBrandingImage);
- attrs.mIconBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
- typedArray.recycle();
- if (DEBUG) {
- Slog.d(TAG, "window attributes color: "
- + Integer.toHexString(attrs.mWindowBgColor)
- + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
- + " brandImage " + attrs.mBrandingImage);
- }
- return attrs;
- }
}
private class StartingWindowViewBuilder {
- private Drawable mThemeBGDrawable;
private Drawable mIconDrawable;
private int mIconAnimationDuration;
private Context mContext;
@@ -203,8 +214,8 @@
private Drawable mFinalIconDrawable;
private float mScale = 1f;
- StartingWindowViewBuilder setThemeDrawable(Drawable background) {
- mThemeBGDrawable = background;
+ StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+ mThemeColor = background;
mBuildComplete = false;
return this;
}
@@ -247,18 +258,14 @@
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
return null;
}
- if (mThemeBGDrawable == null) {
- Slog.w(TAG, "Theme Background Drawable is null, forget to set Theme Drawable?");
- mThemeBGDrawable = createDefaultBackgroundDrawable();
- }
- processThemeColor();
+
if (!processAdaptiveIcon() && mIconDrawable != null) {
if (DEBUG) {
Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
}
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
mIconBackground != Color.TRANSPARENT
- ? mIconBackground : mThemeColor, mIconDrawable);
+ ? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
}
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
@@ -266,22 +273,10 @@
return mCachedResult;
}
- private void createIconDrawable(Drawable iconDrawable) {
+ private void createIconDrawable(Drawable iconDrawable, int iconSize) {
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
mIconBackground != Color.TRANSPARENT
- ? mIconBackground : mThemeColor, iconDrawable);
- }
-
- private void processThemeColor() {
- final DrawableColorTester themeBGTester =
- new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */);
- if (themeBGTester.nonTransparentRatio() == 0) {
- // the window background is transparent, unable to draw
- Slog.w(TAG, "Window background is transparent, fill background with black color");
- mThemeColor = getSystemBGColor();
- } else {
- mThemeColor = themeBGTester.getDominateColor();
- }
+ ? mIconBackground : mThemeColor, iconDrawable, iconSize);
}
private boolean processAdaptiveIcon() {
@@ -289,6 +284,7 @@
return false;
}
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) mIconDrawable;
final DrawableColorTester backIconTester =
new DrawableColorTester(adaptiveIconDrawable.getBackground());
@@ -325,29 +321,28 @@
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
}
- // Using AdaptiveIconDrawable here can help keep the shape consistent with the
- // current settings.
- createIconDrawable(iconForeground);
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
mScale = 1.5f;
}
+ // Using AdaptiveIconDrawable here can help keep the shape consistent with the
+ // current settings.
+ final int iconSize = (int) (0.5f + mIconSize * mScale);
+ createIconDrawable(iconForeground, iconSize);
} else {
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
}
- if (mIconBackground != Color.TRANSPARENT) {
- createIconDrawable(adaptiveIconDrawable);
- } else {
- mFinalIconDrawable = adaptiveIconDrawable;
- }
+ createIconDrawable(adaptiveIconDrawable, mIconSize);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return true;
}
private SplashScreenView fillViewWithIcon(Context context,
int iconSize, Drawable iconDrawable) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
builder.setIconSize(iconSize).setBackgroundColor(mThemeColor)
.setIconBackground(mIconBackground);
@@ -364,6 +359,7 @@
Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
}
splashScreenView.makeSystemUIColorsTransparent();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return splashScreenView;
}
}
@@ -395,7 +391,7 @@
return root < 0.1;
}
- private static SplashScreenView makeSplashscreenContentDrawable(Context ctx,
+ static SplashScreenView makeSplashscreenContent(Context ctx,
int splashscreenContentResId) {
// doesn't support windowSplashscreenContent after S
// TODO add an allowlist to skip some packages if needed
@@ -525,6 +521,7 @@
new TransparentFilterQuantizer();
ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
final Rect initialBounds = drawable.copyBounds();
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
@@ -559,6 +556,7 @@
}
mPalette = builder.generate();
bitmap.recycle();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 8626dbc..a4a83eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -16,11 +16,15 @@
package com.android.wm.shell.startingsurface;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
@@ -28,11 +32,13 @@
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.PathParser;
import android.window.SplashScreenView;
@@ -47,12 +53,57 @@
public class SplashscreenIconDrawableFactory {
static Drawable makeIconDrawable(@ColorInt int backgroundColor,
- @NonNull Drawable foregroundDrawable) {
+ @NonNull Drawable foregroundDrawable, int iconSize) {
if (foregroundDrawable instanceof Animatable) {
return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
+ } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
+ return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
} else {
- // TODO make a light weight drawable instead of AdaptiveIconDrawable
- return new AdaptiveIconDrawable(new ColorDrawable(backgroundColor), foregroundDrawable);
+ return new ImmobileIconDrawable(new AdaptiveIconDrawable(
+ new ColorDrawable(backgroundColor), foregroundDrawable), iconSize);
+ }
+ }
+
+ private static class ImmobileIconDrawable extends Drawable {
+ private Shader mLayersShader;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
+ | Paint.FILTER_BITMAP_FLAG);
+
+ ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) {
+ cachePaint(drawable, iconSize, iconSize);
+ }
+
+ private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
+ final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(layersBitmap);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(canvas);
+ mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP);
+ mPaint.setShader(mLayersShader);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+ canvas.drawRect(bounds, mPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 1;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index b592121..b500813 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -181,11 +181,7 @@
}
int windowFlags = 0;
- final boolean enableHardAccelerated =
- (activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0;
- if (enableHardAccelerated) {
- windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- }
+ windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
final boolean[] showWallpaper = new boolean[1];
final int[] splashscreenContentResId = new int[1];
@@ -246,8 +242,6 @@
params.packageName = activityInfo.packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- params.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
@@ -267,21 +261,31 @@
final int taskId = taskInfo.taskId;
SplashScreenView sView = null;
try {
- sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes,
- splashscreenContentResId[0]);
final View view = win.getDecorView();
final WindowManager wm = mContext.getSystemService(WindowManager.class);
- if (postAddWindow(taskId, appToken, view, wm, params)) {
+ // splash screen content will be deprecated after S.
+ sView = SplashscreenContentDrawer.makeSplashscreenContent(
+ context, splashscreenContentResId[0]);
+ final boolean splashscreenContentCompatible = sView != null;
+ if (splashscreenContentCompatible) {
+ win.setContentView(sView);
+ } else {
+ sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes);
win.setContentView(sView);
sView.cacheRootWindow(win);
}
+ postAddWindow(taskId, appToken, view, wm, params);
} catch (RuntimeException e) {
// don't crash if something else bad happens, for example a
// failure loading resources because we are loading from an app
// on external storage that has been unmounted.
- Slog.w(TAG, " failed creating starting window", e);
+ Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e);
+ sView = null;
} finally {
- setSplashScreenRecord(taskId, sView);
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ record.setSplashScreenView(sView);
+ }
}
}
@@ -328,7 +332,7 @@
ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
}
- protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
+ protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
WindowManager.LayoutParams params) {
boolean shouldSaveView = true;
try {
@@ -349,7 +353,6 @@
removeWindowNoAnimate(taskId);
saveSplashScreenRecord(taskId, view);
}
- return shouldSaveView;
}
private void saveSplashScreenRecord(int taskId, View view) {
@@ -358,13 +361,6 @@
mStartingWindowRecords.put(taskId, tView);
}
- private void setSplashScreenRecord(int taskId, SplashScreenView splashScreenView) {
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- record.setSplashScreenView(splashScreenView);
- }
- }
-
private void removeWindowNoAnimate(int taskId) {
removeWindowSynced(taskId, null, null, false);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 207db9e..8ae0a73 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -83,12 +83,11 @@
}
@Override
- protected boolean postAddWindow(int taskId, IBinder appToken,
+ protected void postAddWindow(int taskId, IBinder appToken,
View view, WindowManager wm, WindowManager.LayoutParams params) {
// listen for addView
mAddWindowForTask = taskId;
mViewThemeResId = view.getContext().getThemeResId();
- return true;
}
@Override
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index b71bb07..1455269 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -120,12 +120,6 @@
int imgHeight = image->height();
sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext());
- if (bitmap->colorType() == kRGBA_F16_SkColorType &&
- !grContext->colorTypeSupportedAsSurface(bitmap->colorType())) {
- ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
- return CopyResult::DestinationInvalid;
- }
-
CopyResult copyResult = CopyResult::UnknownError;
int displayedWidth = imgWidth, displayedHeight = imgHeight;
@@ -159,12 +153,10 @@
bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
SkBitmap* bitmap) {
- /* This intermediate surface is present to work around a bug in SwiftShader that
- * prevents us from reading the contents of the layer's texture directly. The
- * workaround involves first rendering that texture into an intermediate buffer and
- * then reading from the intermediate buffer into the bitmap.
- * Another reason to render in an offscreen buffer is to scale and to avoid an issue b/62262733
- * with reading incorrect data from EGLImage backed SkImage (likely a driver bug).
+ /* This intermediate surface is present to work around limitations that LayerDrawable expects
+ * to render into a GPU backed canvas. Additionally, the offscreen buffer solution works around
+ * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
+ * software buffer.
*/
sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
SkBudgeted::kYes, bitmap->info(), 0,
diff --git a/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
index 50ed046..092ec67f 100644
--- a/location/java/android/location/provider/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -24,7 +24,7 @@
* @hide
*/
interface ILocationProviderManager {
- void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String packageName, @nullable String attributionTag);
+ void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String attributionTag);
void onSetAllowed(boolean allowed);
void onSetProperties(in ProviderProperties properties);
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index ae6395d..eada22c 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
"com.android.location.service.FusedLocationProvider";
private final String mTag;
- private final @Nullable String mPackageName;
private final @Nullable String mAttributionTag;
private final IBinder mBinder;
@@ -108,7 +107,6 @@
public LocationProviderBase(@NonNull Context context, @NonNull String tag,
@NonNull ProviderProperties properties) {
mTag = tag;
- mPackageName = context.getPackageName();
mAttributionTag = context.getAttributionTag();
mBinder = new Service();
@@ -305,7 +303,7 @@
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
- manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+ manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index 85a083e..ade0ea4 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -55,6 +55,20 @@
}
/**
+ * Returns a CallerIdentity with PID and listener ID information stripped. This is mostly
+ * useful for aggregating information for debug purposes, and should not be used in any API with
+ * security requirements.
+ */
+ public static CallerIdentity forAggregation(CallerIdentity callerIdentity) {
+ if (callerIdentity.getPid() == 0 && callerIdentity.getListenerId() == null) {
+ return callerIdentity;
+ }
+
+ return new CallerIdentity(callerIdentity.getUid(), 0, callerIdentity.getPackageName(),
+ callerIdentity.getAttributionTag(), null);
+ }
+
+ /**
* Creates a CallerIdentity for the current process and context.
*/
public static CallerIdentity fromContext(Context context) {
@@ -180,17 +194,6 @@
}
}
- /**
- * Returns a CallerIdentity corrosponding to this CallerIdentity but with a null listener id.
- */
- public CallerIdentity stripListenerId() {
- if (mListenerId == null) {
- return this;
- } else {
- return new CallerIdentity(mUid, mPid, mPackageName, mAttributionTag, null);
- }
- }
-
@Override
public String toString() {
int length = 10 + mPackageName.length();
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 7f1cf6d..95f6c2f 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
final String mTag;
- @Nullable final String mPackageName;
@Nullable final String mAttributionTag;
final IBinder mBinder;
@@ -133,7 +132,6 @@
public LocationProviderBase(Context context, String tag,
ProviderPropertiesUnbundled properties) {
mTag = tag;
- mPackageName = context != null ? context.getPackageName() : null;
mAttributionTag = context != null ? context.getAttributionTag() : null;
mBinder = new Service();
@@ -370,7 +368,7 @@
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
- manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+ manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index 3cd615b..9774e80 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
/**
* An AudioProfile is specific to an audio format and lists supported sampling rates and
* channel masks for that format. An {@link AudioDeviceInfo} has a list of supported AudioProfiles.
@@ -63,4 +66,29 @@
public @NonNull int[] getSampleRates() {
return mSamplingRates;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{");
+ sb.append(AudioFormat.toLogFriendlyEncoding(mFormat));
+ if (mSamplingRates != null && mSamplingRates.length > 0) {
+ sb.append(", sampling rates=").append(Arrays.toString(mSamplingRates));
+ }
+ if (mChannelMasks != null && mChannelMasks.length > 0) {
+ sb.append(", channel masks=").append(toHexString(mChannelMasks));
+ }
+ if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) {
+ sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks));
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String toHexString(int[] ints) {
+ if (ints == null || ints.length == 0) {
+ return "";
+ }
+ return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X, ", anInt))
+ .collect(Collectors.joining());
+ }
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index cf31e41..a7e2b65 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1674,9 +1674,11 @@
public @interface BufferFlag {}
private EventHandler mEventHandler;
+ private EventHandler mOnFirstTunnelFrameReadyHandler;
private EventHandler mOnFrameRenderedHandler;
private EventHandler mCallbackHandler;
private Callback mCallback;
+ private OnFirstTunnelFrameReadyListener mOnFirstTunnelFrameReadyListener;
private OnFrameRenderedListener mOnFrameRenderedListener;
private final Object mListenerLock = new Object();
private MediaCodecInfo mCodecInfo;
@@ -1687,6 +1689,7 @@
private static final int EVENT_CALLBACK = 1;
private static final int EVENT_SET_CALLBACK = 2;
private static final int EVENT_FRAME_RENDERED = 3;
+ private static final int EVENT_FIRST_TUNNEL_FRAME_READY = 4;
private static final int CB_INPUT_AVAILABLE = 1;
private static final int CB_OUTPUT_AVAILABLE = 2;
@@ -1748,6 +1751,16 @@
mCodec, (long)mediaTimeUs, (long)systemNano);
}
break;
+ case EVENT_FIRST_TUNNEL_FRAME_READY:
+ OnFirstTunnelFrameReadyListener onFirstTunnelFrameReadyListener;
+ synchronized (mListenerLock) {
+ onFirstTunnelFrameReadyListener = mOnFirstTunnelFrameReadyListener;
+ }
+ if (onFirstTunnelFrameReadyListener == null) {
+ break;
+ }
+ onFirstTunnelFrameReadyListener.onFirstTunnelFrameReady(mCodec);
+ break;
default:
{
break;
@@ -1923,6 +1936,7 @@
mEventHandler = null;
}
mCallbackHandler = mEventHandler;
+ mOnFirstTunnelFrameReadyHandler = mEventHandler;
mOnFrameRenderedHandler = mEventHandler;
mBufferLock = new Object();
@@ -2277,6 +2291,9 @@
mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
mCallbackHandler.removeMessages(EVENT_CALLBACK);
}
+ if (mOnFirstTunnelFrameReadyHandler != null) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
if (mOnFrameRenderedHandler != null) {
mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
}
@@ -4447,6 +4464,41 @@
MediaFormat.KEY_LOW_LATENCY;
/**
+ * Control video peek of the first frame when a codec is configured for tunnel mode with
+ * {@link MediaFormat#KEY_AUDIO_SESSION_ID} while the {@link AudioTrack} is paused.
+ *<p>
+ * When disabled (1) after a {@link #flush} or {@link #start}, (2) while the corresponding
+ * {@link AudioTrack} is paused and (3) before any buffers are queued, the first frame is not to
+ * be rendered until either this parameter is enabled or the corresponding {@link AudioTrack}
+ * has begun playback. Once the frame is decoded and ready to be rendered,
+ * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called but the frame is
+ * not rendered. The surface continues to show the previously-rendered content, or black if the
+ * surface is new. A subsequent call to {@link AudioTrack#play} renders this frame and triggers
+ * a callback to {@link OnFrameRenderedListener#onFrameRendered}, and video playback begins.
+ *<p>
+ * <b>Note</b>: To clear any previously rendered content and show black, configure the
+ * MediaCodec with {@code KEY_PUSH_BLANK_BUFFERS_ON_STOP(1)}, and call {@link #stop} before
+ * pushing new video frames to the codec.
+ *<p>
+ * When enabled (1) after a {@link #flush} or {@link #start} and (2) while the corresponding
+ * {@link AudioTrack} is paused, the first frame is rendered as soon as it is decoded, or
+ * immediately, if it has already been decoded. If not already decoded, when the frame is
+ * decoded and ready to be rendered,
+ * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called. The frame is then
+ * immediately rendered and {@link OnFrameRenderedListener#onFrameRendered} is subsequently
+ * called.
+ *<p>
+ * The value is an Integer object containing the value 1 to enable or the value 0 to disable.
+ *<p>
+ * The default for this parameter is <b>enabled</b>. Once a frame has been rendered, changing
+ * this parameter has no effect until a subsequent {@link #flush} or
+ * {@link #stop}/{@link #start}.
+ *
+ * @see #setParameters(Bundle)
+ */
+ public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek";
+
+ /**
* Communicate additional parameter changes to the component instance.
* <b>Note:</b> Some of these parameter changes may silently fail to apply.
*
@@ -4545,6 +4597,55 @@
}
/**
+ * Listener to be called when the first output frame has been decoded
+ * and is ready to be rendered for a codec configured for tunnel mode with
+ * {@code KEY_AUDIO_SESSION_ID}.
+ *
+ * @see MediaCodec#setOnFirstTunnelFrameReadyListener
+ */
+ public interface OnFirstTunnelFrameReadyListener {
+
+ /**
+ * Called when the first output frame has been decoded and is ready to be
+ * rendered.
+ */
+ void onFirstTunnelFrameReady(@NonNull MediaCodec codec);
+ }
+
+ /**
+ * Registers a callback to be invoked when the first output frame has been decoded
+ * and is ready to be rendered on a codec configured for tunnel mode with {@code
+ * KEY_AUDIO_SESSION_ID}.
+ *
+ * @param handler the callback will be run on the handler's thread. If {@code
+ * null}, the callback will be run on the default thread, which is the looper from
+ * which the codec was created, or a new thread if there was none.
+ *
+ * @param listener the callback that will be run. If {@code null}, clears any registered
+ * listener.
+ */
+ public void setOnFirstTunnelFrameReadyListener(
+ @Nullable Handler handler, @Nullable OnFirstTunnelFrameReadyListener listener) {
+ synchronized (mListenerLock) {
+ mOnFirstTunnelFrameReadyListener = listener;
+ if (listener != null) {
+ EventHandler newHandler = getEventHandlerOn(
+ handler,
+ mOnFirstTunnelFrameReadyHandler);
+ if (newHandler != mOnFirstTunnelFrameReadyHandler) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
+ mOnFirstTunnelFrameReadyHandler = newHandler;
+ } else if (mOnFirstTunnelFrameReadyHandler != null) {
+ mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+ }
+ native_enableOnFirstTunnelFrameReadyListener(listener != null);
+ }
+ }
+
+ private native void native_enableOnFirstTunnelFrameReadyListener(boolean enable);
+
+ /**
* Listener to be called when an output frame has rendered on the output surface
*
* @see MediaCodec#setOnFrameRenderedListener
@@ -4667,6 +4768,8 @@
EventHandler handler = mEventHandler;
if (what == EVENT_CALLBACK) {
handler = mCallbackHandler;
+ } else if (what == EVENT_FIRST_TUNNEL_FRAME_READY) {
+ handler = mOnFirstTunnelFrameReadyHandler;
} else if (what == EVENT_FRAME_RENDERED) {
handler = mOnFrameRenderedHandler;
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 3de78bb..644afb7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -99,9 +99,7 @@
/**
- * MediaPlayer class can be used to control playback
- * of audio/video files and streams. An example on how to use the methods in
- * this class can be found in {@link android.widget.VideoView}.
+ * MediaPlayer class can be used to control playback of audio/video files and streams.
*
* <p>MediaPlayer is not thread-safe. Creation of and all access to player instances
* should be on the same thread. If registering <a href="#Callbacks">callbacks</a>,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 02fa040..8daa303 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -299,6 +299,24 @@
}
/**
+ * Registers a callback to receive route related events when they change.
+ * <p>
+ * If the specified callback is already registered, its registration will be updated for the
+ * given {@link Executor executor}.
+ * <p>
+ * This will be no-op for non-system routers.
+ * @hide
+ */
+ @SystemApi
+ public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull RouteCallback routeCallback) {
+ if (!isSystemRouter()) {
+ return;
+ }
+ registerRouteCallback(executor, routeCallback, RouteDiscoveryPreference.EMPTY);
+ }
+
+ /**
* Registers a callback to discover routes and to receive events when they change.
* <p>
* If the specified callback is already registered, its registration will be updated for the
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 7f5dd5d..ded2e1b 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -81,6 +81,7 @@
EVENT_CALLBACK = 1,
EVENT_SET_CALLBACK = 2,
EVENT_FRAME_RENDERED = 3,
+ EVENT_FIRST_TUNNEL_FRAME_READY = 4,
};
static struct CryptoErrorCodes {
@@ -269,6 +270,18 @@
mClass = NULL;
}
+status_t JMediaCodec::enableOnFirstTunnelFrameReadyListener(jboolean enable) {
+ if (enable) {
+ if (mOnFirstTunnelFrameReadyNotification == NULL) {
+ mOnFirstTunnelFrameReadyNotification = new AMessage(kWhatFirstTunnelFrameReady, this);
+ }
+ } else {
+ mOnFirstTunnelFrameReadyNotification.clear();
+ }
+
+ return mCodec->setOnFirstTunnelFrameReadyNotification(mOnFirstTunnelFrameReadyNotification);
+}
+
status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) {
if (enable) {
if (mOnFrameRenderedNotification == NULL) {
@@ -1058,6 +1071,27 @@
env->DeleteLocalRef(obj);
}
+void JMediaCodec::handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg) {
+ int32_t arg1 = 0, arg2 = 0;
+ jobject obj = NULL;
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ sp<AMessage> data;
+ CHECK(msg->findMessage("data", &data));
+
+ status_t err = ConvertMessageToMap(env, data, &obj);
+ if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ env->CallVoidMethod(
+ mObject, gFields.postEventFromNativeID,
+ EVENT_FIRST_TUNNEL_FRAME_READY, arg1, arg2, obj);
+
+ env->DeleteLocalRef(obj);
+}
+
void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) {
int32_t arg1 = 0, arg2 = 0;
jobject obj = NULL;
@@ -1100,6 +1134,11 @@
}
break;
}
+ case kWhatFirstTunnelFrameReady:
+ {
+ handleFirstTunnelFrameReadyNotification(msg);
+ break;
+ }
default:
TRESPASS();
}
@@ -1256,6 +1295,22 @@
}
}
+static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener(
+ JNIEnv *env,
+ jobject thiz,
+ jboolean enabled) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled);
+
+ throwExceptionAsNecessary(env, err);
+}
+
static void android_media_MediaCodec_native_enableOnFrameRenderedListener(
JNIEnv *env,
jobject thiz,
@@ -3138,6 +3193,9 @@
{ "native_setInputSurface", "(Landroid/view/Surface;)V",
(void *)android_media_MediaCodec_setInputSurface },
+ { "native_enableOnFirstTunnelFrameReadyListener", "(Z)V",
+ (void *)android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener },
+
{ "native_enableOnFrameRenderedListener", "(Z)V",
(void *)android_media_MediaCodec_native_enableOnFrameRenderedListener },
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index f16bcf3..5fd6bfd 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -63,6 +63,8 @@
void release();
void releaseAsync();
+ status_t enableOnFirstTunnelFrameReadyListener(jboolean enable);
+
status_t enableOnFrameRenderedListener(jboolean enable);
status_t setCallback(jobject cb);
@@ -176,6 +178,7 @@
kWhatCallbackNotify,
kWhatFrameRendered,
kWhatAsyncReleaseComplete,
+ kWhatFirstTunnelFrameReady,
};
jclass mClass;
@@ -191,6 +194,7 @@
std::once_flag mAsyncReleaseFlag;
sp<AMessage> mCallbackNotification;
+ sp<AMessage> mOnFirstTunnelFrameReadyNotification;
sp<AMessage> mOnFrameRenderedNotification;
status_t mInitStatus;
@@ -203,6 +207,7 @@
jobject *buf) const;
void handleCallback(const sp<AMessage> &msg);
+ void handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg);
void handleFrameRenderedNotification(const sp<AMessage> &msg);
DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b01878b..85513ca 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -285,6 +285,7 @@
ATrace_endAsyncSection; # introduced=29
ATrace_setCounter; # introduced=29
android_getaddrinfofornetwork; # introduced=23
+ android_getprocnetwork; # introduced=31
android_setprocnetwork; # introduced=23
android_setsocknetwork; # introduced=23
android_res_cancel; # introduced=29
@@ -309,4 +310,4 @@
ASurfaceControlStats_getAcquireTime*;
ASurfaceControlStats_getFrameNumber*;
};
-} LIBANDROID;
\ No newline at end of file
+} LIBANDROID;
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index 8d4e900..cc8dd72 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -14,6 +14,8 @@
android_res_nquery; # llndk
android_res_nresult; # llndk
android_res_nsend; # llndk
+ # These functions have been part of the NDK since API 31.
+ android_getprocnetwork; # llndk
local:
*;
};
diff --git a/native/android/net.c b/native/android/net.c
index a8104fc..d4b8888 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -22,12 +22,13 @@
#include <stdlib.h>
#include <sys/limits.h>
+// This value MUST be kept in sync with the corresponding value in
+// the android.net.Network#getNetworkHandle() implementation.
+static const uint32_t kHandleMagic = 0xcafed00d;
+static const uint32_t kHandleMagicSize = 32;
static int getnetidfromhandle(net_handle_t handle, unsigned *netid) {
static const uint32_t k32BitMask = 0xffffffff;
- // This value MUST be kept in sync with the corresponding value in
- // the android.net.Network#getNetworkHandle() implementation.
- static const uint32_t kHandleMagic = 0xcafed00d;
// Check for minimum acceptable version of the API in the low bits.
if (handle != NETWORK_UNSPECIFIED &&
@@ -41,6 +42,12 @@
return 1;
}
+static net_handle_t gethandlefromnetid(unsigned netid) {
+ if (netid == NETID_UNSET) {
+ return NETWORK_UNSPECIFIED;
+ }
+ return (((net_handle_t) netid) << kHandleMagicSize) | kHandleMagic;
+}
int android_setsocknetwork(net_handle_t network, int fd) {
unsigned netid;
@@ -72,6 +79,17 @@
return rval;
}
+int android_getprocnetwork(net_handle_t *network) {
+ if (network == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ unsigned netid = getNetworkForProcess();
+ *network = gethandlefromnetid(netid);
+ return 0;
+}
+
int android_getaddrinfofornetwork(net_handle_t network,
const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
diff --git a/packages/Connectivity/TEST_MAPPING b/packages/Connectivity/TEST_MAPPING
new file mode 100644
index 0000000..94f9232
--- /dev/null
+++ b/packages/Connectivity/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/java/android/net"
+ },
+ {
+ "path": "packages/modules/NetworkStack"
+ },
+ {
+ "path": "packages/modules/CaptivePortalLogin"
+ },
+ {
+ "path": "packages/modules/Connectivity"
+ },
+ {
+ "path": "packages/modules/Connectivity/Tethering"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 657d5a3..3553c1f 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -128,6 +128,7 @@
srcs: [
"jni/android_net_NetworkUtils.cpp",
],
+ shared_libs: ["libandroid_net"],
apex_available: [
"//apex_available:platform",
"com.android.tethering",
@@ -140,6 +141,7 @@
srcs: [
"jni/onload.cpp",
],
+ shared_libs: ["libandroid"],
static_libs: ["libconnectivityframeworkutils"],
apex_available: [
"//apex_available:platform",
diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt
index e415e01..0a9560a 100644
--- a/packages/Connectivity/framework/api/current.txt
+++ b/packages/Connectivity/framework/api/current.txt
@@ -68,6 +68,7 @@
method public boolean bindProcessToNetwork(@Nullable android.net.Network);
method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+ method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public android.net.Network getActiveNetworkForUid(int);
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
@@ -387,7 +388,9 @@
public class NetworkRequest implements android.os.Parcelable {
method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
method public int describeContents();
+ method @NonNull public int[] getCapabilities();
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+ method @NonNull public int[] getTransportTypes();
method public boolean hasCapability(int);
method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
@@ -396,6 +399,7 @@
public static class NetworkRequest.Builder {
ctor public NetworkRequest.Builder();
+ ctor public NetworkRequest.Builder(@NonNull android.net.NetworkRequest);
method public android.net.NetworkRequest.Builder addCapability(int);
method public android.net.NetworkRequest.Builder addTransportType(int);
method public android.net.NetworkRequest build();
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 9376a4b..8b99d21 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -13,11 +13,14 @@
method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+ method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
method public void systemReady();
@@ -42,6 +45,7 @@
public final class NetworkCapabilities implements android.os.Parcelable {
ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long);
method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
+ method public boolean hasUnwantedCapability(int);
field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
@@ -54,7 +58,14 @@
method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
+ public class NetworkRequest implements android.os.Parcelable {
+ method @NonNull public int[] getUnwantedCapabilities();
+ method public boolean hasUnwantedCapability(int);
+ }
+
public static class NetworkRequest.Builder {
+ method @NonNull public android.net.NetworkRequest.Builder addUnwantedCapability(int);
+ method @NonNull public android.net.NetworkRequest.Builder removeUnwantedCapability(int);
method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 358cea8..b19efa3 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -52,7 +52,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
- method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor);
+ method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.QosCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
@@ -212,10 +212,12 @@
public abstract class NetworkAgent {
ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+ ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
method @Nullable public android.net.Network getNetwork();
method public void markConnected();
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
method public void onAutomaticReconnectDisabled();
+ method public void onBandwidthUpdateRequested();
method public void onNetworkUnwanted();
method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
method public void onQosCallbackUnregistered(int);
@@ -233,6 +235,7 @@
method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes);
method public final void sendQosSessionLost(int, int);
method public final void sendSocketKeepaliveEvent(int, int);
+ method @Deprecated public void setLegacySubtype(int, @NonNull String);
method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method public void unregister();
field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
@@ -253,7 +256,12 @@
public static final class NetworkAgentConfig.Builder {
ctor public NetworkAgentConfig.Builder();
method @NonNull public android.net.NetworkAgentConfig build();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
@@ -316,6 +324,19 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
}
+ public final class NetworkScore implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getLegacyInt();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
+ }
+
+ public static final class NetworkScore.Builder {
+ ctor public NetworkScore.Builder();
+ method @NonNull public android.net.NetworkScore build();
+ method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+ }
+
public final class OemNetworkPreferences implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
@@ -388,6 +409,7 @@
}
public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
field public static final int SUCCESS = 0; // 0x0
}
diff --git a/packages/Connectivity/framework/jarjar-rules.txt b/packages/Connectivity/framework/jarjar-rules.txt
index 0959840..7474c24 100644
--- a/packages/Connectivity/framework/jarjar-rules.txt
+++ b/packages/Connectivity/framework/jarjar-rules.txt
@@ -1,4 +1,5 @@
rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
+rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
# TODO (b/149403767): remove the annotations from net-utils-device-common instead of here
zap android.annotation.**
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index c5b1ff8..c7c0bee 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -19,6 +19,7 @@
#include <vector>
#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
#include <arpa/inet.h>
#include <linux/filter.h>
#include <linux/if_arp.h>
@@ -94,14 +95,21 @@
}
}
-static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
+ jlong netHandle)
{
- return (jboolean) !setNetworkForProcess(netId);
+ return (jboolean) !android_setprocnetwork(netHandle);
}
-static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz)
+static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
{
- return getNetworkForProcess();
+ net_handle_t network;
+ if (android_getprocnetwork(&network) != 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "android_getprocnetwork(): %s", strerror(errno));
+ return NETWORK_UNSPECIFIED;
+ }
+ return (jlong) network;
}
static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
@@ -255,8 +263,8 @@
// clang-format off
static const JNINativeMethod gNetworkUtilMethods[] = {
/* name, signature, funcPtr */
- { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
- { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
+ { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
+ { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
{ "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index f8a0e4e..d490618 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -1083,8 +1083,7 @@
*
* @return a {@link Network} object for the current default network for the
* given UID or {@code null} if no default network is currently active
- *
- * @hide
+ * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed
*/
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
@Nullable
@@ -1125,12 +1124,13 @@
* @param ranges the UID ranges to restrict
* @param requireVpn whether the specified UID ranges must use a VPN
*
- * TODO: expose as @SystemApi.
* @hide
*/
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
- android.Manifest.permission.NETWORK_STACK})
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ @SystemApi(client = MODULE_LIBRARIES)
public void setRequireVpnForUids(boolean requireVpn,
@NonNull Collection<Range<Integer>> ranges) {
Objects.requireNonNull(ranges);
@@ -1174,13 +1174,13 @@
*
* @param enabled whether legacy lockdown VPN is enabled or disabled
*
- * TODO: @SystemApi(client = MODULE_LIBRARIES)
- *
* @hide
*/
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS})
+ @SystemApi(client = MODULE_LIBRARIES)
public void setLegacyLockdownVpnEnabled(boolean enabled) {
try {
mService.setLegacyLockdownVpnEnabled(enabled);
@@ -2127,6 +2127,7 @@
*/
@Deprecated
@UnsupportedAppUsage
+ @SystemApi(client = MODULE_LIBRARIES)
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
checkLegacyRoutingApiAccess();
try {
@@ -4942,20 +4943,20 @@
* {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}.
*
* @param socketInfo the socket information used to match QoS events
- * @param callback receives qos events that satisfy socketInfo
* @param executor The executor on which the callback will be invoked. The provided
* {@link Executor} must run callback sequentially, otherwise the order of
- * callbacks cannot be guaranteed.
+ * callbacks cannot be guaranteed.onQosCallbackRegistered
+ * @param callback receives qos events that satisfy socketInfo
*
* @hide
*/
@SystemApi
public void registerQosCallback(@NonNull final QosSocketInfo socketInfo,
- @NonNull final QosCallback callback,
- @CallbackExecutor @NonNull final Executor executor) {
+ @CallbackExecutor @NonNull final Executor executor,
+ @NonNull final QosCallback callback) {
Objects.requireNonNull(socketInfo, "socketInfo must be non-null");
- Objects.requireNonNull(callback, "callback must be non-null");
Objects.requireNonNull(executor, "executor must be non-null");
+ Objects.requireNonNull(callback, "callback must be non-null");
try {
synchronized (mQosCallbackConnections) {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 3863ed1..b3d9616 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -362,9 +362,8 @@
public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
- // The subtype can be changed with (TODO) setLegacySubtype, but it starts
- // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description.
- final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, "");
+ final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
+ config.legacyTypeName, config.legacySubTypeName);
ni.setIsAvailable(true);
ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */,
config.getLegacyExtraInfo());
@@ -390,7 +389,6 @@
* @param score the initial score of this network. Update with sendNetworkScore.
* @param config an immutable {@link NetworkAgentConfig} for this agent.
* @param provider the {@link NetworkProvider} managing this agent.
- * @hide TODO : unhide when impl is complete
*/
public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
@NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
@@ -829,6 +827,7 @@
* @hide
*/
@Deprecated
+ @SystemApi
public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
queueOrSendNetworkInfo(mNetworkInfo);
@@ -962,6 +961,7 @@
* shall try to overwrite this method and produce a bandwidth update if capable.
* @hide
*/
+ @SystemApi
public void onBandwidthUpdateRequested() {
pollLceData();
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
index 5e50a64..fb6fcc1 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
@@ -165,6 +165,12 @@
}
/**
+ * The legacy Sub type of this network agent, or TYPE_NONE if unset.
+ * @hide
+ */
+ public int legacySubType = ConnectivityManager.TYPE_NONE;
+
+ /**
* Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
* Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
*
@@ -190,6 +196,13 @@
}
/**
+ * The name of the legacy Sub network type. It's a free-form string.
+ * @hide
+ */
+ @NonNull
+ public String legacySubTypeName = "";
+
+ /**
* The legacy extra info of the agent. The extra info should only be :
* <ul>
* <li>For cellular agents, the APN name.</li>
@@ -225,6 +238,8 @@
skip464xlat = nac.skip464xlat;
legacyType = nac.legacyType;
legacyTypeName = nac.legacyTypeName;
+ legacySubType = nac.legacySubType;
+ legacySubTypeName = nac.legacySubTypeName;
mLegacyExtraInfo = nac.mLegacyExtraInfo;
}
}
@@ -290,7 +305,6 @@
* and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableNat64Detection() {
@@ -303,7 +317,6 @@
* perform its own carrier-specific provisioning procedure.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableProvisioningNotification() {
@@ -324,6 +337,18 @@
}
/**
+ * Sets the legacy sub-type for this network.
+ *
+ * @param legacySubType the type
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubType(final int legacySubType) {
+ mConfig.legacySubType = legacySubType;
+ return this;
+ }
+
+ /**
* Sets the name of the legacy type of the agent. It's a free-form string used in logging.
* @param legacyTypeName the name
* @return this builder, to facilitate chaining.
@@ -335,10 +360,20 @@
}
/**
+ * Sets the name of the legacy Sub-type of the agent. It's a free-form string.
+ * @param legacySubTypeName the name
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubTypeName(@NonNull String legacySubTypeName) {
+ mConfig.legacySubTypeName = legacySubTypeName;
+ return this;
+ }
+
+ /**
* Sets the legacy extra info of the agent.
* @param legacyExtraInfo the legacy extra info.
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) {
@@ -412,6 +447,8 @@
out.writeInt(skip464xlat ? 1 : 0);
out.writeInt(legacyType);
out.writeString(legacyTypeName);
+ out.writeInt(legacySubType);
+ out.writeString(legacySubTypeName);
out.writeString(mLegacyExtraInfo);
}
@@ -429,6 +466,8 @@
networkAgentConfig.skip464xlat = in.readInt() != 0;
networkAgentConfig.legacyType = in.readInt();
networkAgentConfig.legacyTypeName = in.readString();
+ networkAgentConfig.legacySubType = in.readInt();
+ networkAgentConfig.legacySubTypeName = in.readString();
networkAgentConfig.mLegacyExtraInfo = in.readString();
return networkAgentConfig;
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 6572bbde..4924851 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -602,19 +602,31 @@
}
/**
- * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+ * Removes (if found) the given capability from this {@code NetworkCapability}
+ * instance that were added via addCapability(int) or setCapabilities(int[], int[]).
*
* @param capability the capability to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
- // Note that this method removes capabilities that were added via addCapability(int),
- // addUnwantedCapability(int) or setCapabilities(int[], int[]).
checkValidCapability(capability);
final long mask = ~(1 << capability);
mNetworkCapabilities &= mask;
- mUnwantedNetworkCapabilities &= mask;
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given unwanted capability from this {@code NetworkCapability}
+ * instance that were added via addUnwantedCapability(int) or setCapabilities(int[], int[]).
+ *
+ * @param capability the capability to be removed.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) {
+ checkValidCapability(capability);
+ mUnwantedNetworkCapabilities &= ~(1 << capability);
return this;
}
@@ -686,6 +698,7 @@
}
/** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public boolean hasUnwantedCapability(@NetCapability int capability) {
return isValidCapability(capability)
&& ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
@@ -699,10 +712,16 @@
return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
}
- /** Note this method may result in having the same capability in wanted and unwanted lists. */
private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
- this.mNetworkCapabilities |= nc.mNetworkCapabilities;
- this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities;
+ final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
+ final long unwantedCaps =
+ this.mUnwantedNetworkCapabilities | nc.mUnwantedNetworkCapabilities;
+ if ((wantedCaps & unwantedCaps) != 0) {
+ throw new IllegalArgumentException(
+ "Cannot have the same capability in wanted and unwanted lists.");
+ }
+ this.mNetworkCapabilities = wantedCaps;
+ this.mUnwantedNetworkCapabilities = unwantedCaps;
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index cf131f0..38691ef 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -216,6 +216,14 @@
}
/**
+ * Creates a new Builder of NetworkRequest from an existing instance.
+ */
+ public Builder(@NonNull final NetworkRequest request) {
+ Objects.requireNonNull(request);
+ mNetworkCapabilities = request.networkCapabilities;
+ }
+
+ /**
* Build {@link NetworkRequest} give the current set of capabilities.
*/
public NetworkRequest build() {
@@ -305,12 +313,31 @@
*
* @hide
*/
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addUnwantedCapability(capability);
return this;
}
/**
+ * Removes (if found) the given unwanted capability from this builder instance.
+ *
+ * @param capability The unwanted capability to remove.
+ * @return The builder to facilitate chaining.
+ *
+ * @hide
+ */
+ @NonNull
+ @SuppressLint("BuilderSetStyle")
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public Builder removeUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
+ mNetworkCapabilities.removeUnwantedCapability(capability);
+ return this;
+ }
+
+ /**
* Completely clears all the {@code NetworkCapabilities} from this builder instance,
* removing even the capabilities that are set by default when the object is constructed.
*
@@ -567,6 +594,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public boolean hasUnwantedCapability(@NetCapability int capability) {
return networkCapabilities.hasUnwantedCapability(capability);
}
@@ -671,4 +699,43 @@
public int hashCode() {
return Objects.hash(requestId, legacyType, networkCapabilities, type);
}
+
+ /**
+ * Gets all the capabilities set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of capability values for this instance.
+ */
+ @NonNull
+ public @NetCapability int[] getCapabilities() {
+ // No need to make a defensive copy here as NC#getCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getCapabilities();
+ }
+
+ /**
+ * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of unwanted capability values for this instance.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @NetCapability int[] getUnwantedCapabilities() {
+ // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getUnwantedCapabilities();
+ }
+
+ /**
+ * Gets all the transports set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of transport type values for this instance.
+ */
+ @NonNull
+ public @Transport int[] getTransportTypes() {
+ // No need to make a defensive copy here as NC#getTransportTypes() already returns
+ // a new array.
+ return networkCapabilities.getTransportTypes();
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java
index eadcb2d..6584993 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkScore.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -29,7 +30,7 @@
* network is considered for a particular use.
* @hide
*/
-// TODO : @SystemApi when the implementation is complete
+@SystemApi
public final class NetworkScore implements Parcelable {
// This will be removed soon. Do *NOT* depend on it for any new code that is not part of
// a migration.
@@ -62,6 +63,8 @@
/**
* @return whether this score has a particular policy.
+ *
+ * @hide
*/
@VisibleForTesting
public boolean hasPolicy(final int policy) {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index c0f2628..16ae55f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.net.ConnectivityManager.NETID_UNSET;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.system.ErrnoException;
@@ -55,6 +57,8 @@
*/
public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
+ private static native boolean bindProcessToNetworkHandle(long netHandle);
+
/**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
@@ -63,13 +67,20 @@
* is by design so an application doesn't accidentally use sockets it thinks are still bound to
* a particular {@code Network}. Passing NETID_UNSET clears the binding.
*/
- public native static boolean bindProcessToNetwork(int netId);
+ public static boolean bindProcessToNetwork(int netId) {
+ return bindProcessToNetworkHandle(new Network(netId).getNetworkHandle());
+ }
+
+ private static native long getBoundNetworkHandleForProcess();
/**
* Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
* {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
*/
- public native static int getBoundNetworkForProcess();
+ public static int getBoundNetworkForProcess() {
+ final long netHandle = getBoundNetworkHandleForProcess();
+ return netHandle == 0L ? NETID_UNSET : Network.fromNetworkHandle(netHandle).getNetId();
+ }
/**
* Binds host resolutions performed by this process to the network designated by {@code netId}.
@@ -323,22 +334,7 @@
*/
@UnsupportedAppUsage
public static String trimV4AddrZeros(String addr) {
- if (addr == null) return null;
- String[] octets = addr.split("\\.");
- if (octets.length != 4) return addr;
- StringBuilder builder = new StringBuilder(16);
- String result = null;
- for (int i = 0; i < 4; i++) {
- try {
- if (octets[i].length() > 3) return addr;
- builder.append(Integer.parseInt(octets[i]));
- } catch (NumberFormatException e) {
- return addr;
- }
- if (i < 3) builder.append('.');
- }
- result = builder.toString();
- return result;
+ return Inet4AddressUtils.trimAddressZeros(addr);
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
index d007a95..f6cae72 100644
--- a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
+++ b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
@@ -55,36 +55,68 @@
static final String TAG = "SocketKeepalive";
/**
- * No errors.
+ * Success. It indicates there is no error.
* @hide
*/
@SystemApi
public static final int SUCCESS = 0;
- /** @hide */
+ /**
+ * No keepalive. This should only be internally as it indicates There is no keepalive.
+ * It should not propagate to applications.
+ * @hide
+ */
public static final int NO_KEEPALIVE = -1;
- /** @hide */
+ /**
+ * Data received.
+ * @hide
+ */
public static final int DATA_RECEIVED = -2;
- /** @hide */
+ /**
+ * The binder died.
+ * @hide
+ */
public static final int BINDER_DIED = -10;
- /** The specified {@code Network} is not connected. */
+ /**
+ * The invalid network. It indicates the specified {@code Network} is not connected.
+ */
public static final int ERROR_INVALID_NETWORK = -20;
- /** The specified IP addresses are invalid. For example, the specified source IP address is
- * not configured on the specified {@code Network}. */
+
+ /**
+ * The invalid IP addresses. Indicates the specified IP addresses are invalid.
+ * For example, the specified source IP address is not configured on the
+ * specified {@code Network}.
+ */
public static final int ERROR_INVALID_IP_ADDRESS = -21;
- /** The requested port is invalid. */
+
+ /**
+ * The port is invalid.
+ */
public static final int ERROR_INVALID_PORT = -22;
- /** The packet length is invalid (e.g., too long). */
+
+ /**
+ * The length is invalid (e.g. too long).
+ */
public static final int ERROR_INVALID_LENGTH = -23;
- /** The packet transmission interval is invalid (e.g., too short). */
+
+ /**
+ * The interval is invalid (e.g. too short).
+ */
public static final int ERROR_INVALID_INTERVAL = -24;
- /** The target socket is invalid. */
+
+ /**
+ * The socket is invalid.
+ */
public static final int ERROR_INVALID_SOCKET = -25;
- /** The target socket is not idle. */
+
+ /**
+ * The socket is not idle.
+ */
public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
/**
* The stop reason is uninitialized. This should only be internally used as initial state
* of stop reason, instead of propagating to application.
@@ -92,15 +124,29 @@
*/
public static final int ERROR_STOP_REASON_UNINITIALIZED = -27;
- /** The device does not support this request. */
+ /**
+ * The request is unsupported.
+ */
public static final int ERROR_UNSUPPORTED = -30;
- /** @hide TODO: delete when telephony code has been updated. */
- public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
- /** The hardware returned an error. */
+
+ /**
+ * There was a hardware error.
+ */
public static final int ERROR_HARDWARE_ERROR = -31;
- /** The limitation of resource is reached. */
+
+ /**
+ * Resources are insufficient (e.g. all hardware slots are in use).
+ */
public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
+ /**
+ * There was no such slot. This should only be internally as it indicates
+ * a programming error in the system server. It should not propagate to
+ * applications.
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_NO_SUCH_SLOT = -33;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -111,7 +157,8 @@
ERROR_INVALID_LENGTH,
ERROR_INVALID_INTERVAL,
ERROR_INVALID_SOCKET,
- ERROR_SOCKET_NOT_IDLE
+ ERROR_SOCKET_NOT_IDLE,
+ ERROR_NO_SUCH_SLOT
})
public @interface ErrorCode {}
@@ -122,7 +169,6 @@
ERROR_INVALID_LENGTH,
ERROR_UNSUPPORTED,
ERROR_INSUFFICIENT_RESOURCES,
- ERROR_HARDWARE_UNSUPPORTED
})
public @interface KeepaliveEvent {}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index f630cea..37dd9ff 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -51,23 +51,35 @@
java_library {
name: "service-connectivity-pre-jarjar",
+ sdk_version: "system_server_current",
srcs: [
- ":framework-connectivity-shared-srcs",
":connectivity-service-srcs",
+ ":framework-connectivity-shared-srcs",
+ ":services-connectivity-shared-srcs",
+ // TODO: move to net-utils-device-common, enable shrink optimization to avoid extra classes
+ ":net-module-utils-srcs",
],
libs: [
- "android.net.ipsec.ike",
- "services.core",
- "services.net",
+ // TODO (b/183097033) remove once system_server_current includes core_current
+ "stable.core.platform.api.stubs",
+ "android_system_server_stubs_current",
+ "framework-annotations-lib",
+ "framework-connectivity.impl",
+ "framework-tethering.stubs.module_lib",
+ "framework-wifi.stubs.module_lib",
"unsupportedappusage",
"ServiceConnectivityResources",
],
static_libs: [
+ "dnsresolver_aidl_interface-V7-java",
"modules-utils-os",
"net-utils-device-common",
"net-utils-framework-common",
"netd-client",
+ "netlink-client",
+ "networkstack-client",
"PlatformProperties",
+ "service-connectivity-protos",
],
apex_available: [
"//apex_available:platform",
@@ -76,7 +88,24 @@
}
java_library {
+ name: "service-connectivity-protos",
+ sdk_version: "system_current",
+ proto: {
+ type: "nano",
+ },
+ srcs: [
+ ":system-messages-proto-src",
+ ],
+ libs: ["libprotobuf-java-nano"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
+}
+
+java_library {
name: "service-connectivity",
+ sdk_version: "system_server_current",
installable: true,
static_libs: [
"service-connectivity-pre-jarjar",
diff --git a/packages/Connectivity/service/jarjar-rules.txt b/packages/Connectivity/service/jarjar-rules.txt
index a7b419b..5caa11b 100644
--- a/packages/Connectivity/service/jarjar-rules.txt
+++ b/packages/Connectivity/service/jarjar-rules.txt
@@ -12,3 +12,6 @@
# the one in com.android.internal.util
rule android.util.IndentingPrintWriter* android.connectivity.util.IndentingPrintWriter@1
rule com.android.internal.util.** com.android.connectivity.util.@1
+
+rule com.android.internal.messages.** com.android.connectivity.messages.@1
+rule com.google.protobuf.** com.android.connectivity.protobuf.@1
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/packages/Connectivity/service/proto/connectivityproto.proto
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to packages/Connectivity/service/proto/connectivityproto.proto
index cab5ad2..a992d7c 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/packages/Connectivity/service/proto/connectivityproto.proto
@@ -14,17 +14,7 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+syntax = "proto2";
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+// Connectivity protos can be created in this directory. Note this file must be included before
+// building system-messages-proto, otherwise it will not build by itself.
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 7cc5994..c4c60ea 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -151,20 +151,14 @@
}
@Override
- public void onInitialize(boolean allowed, ProviderProperties properties, String packageName,
- String attributionTag) {
-
- }
+ public void onInitialize(boolean allowed, ProviderProperties properties,
+ String attributionTag) {}
@Override
- public void onSetAllowed(boolean allowed) {
-
- }
+ public void onSetAllowed(boolean allowed) {}
@Override
- public void onSetProperties(ProviderProperties properties) {
-
- }
+ public void onSetProperties(ProviderProperties properties) {}
@Override
public void onReportLocation(Location location) {
@@ -177,9 +171,7 @@
}
@Override
- public void onFlushComplete() {
-
- }
+ public void onFlushComplete() {}
public Location getNextLocation(long timeoutMs) throws InterruptedException {
return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d3e4d1c..1c112c6 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -211,7 +211,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Wi-Fi ሲገናኝ የማረም ሁነታ"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"ስህተት"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"ገመድ-አልባ debugging"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ debuggingን ያብሩ"</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ ማረምን ያብሩ"</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"የQR ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"የQR ኮድ መቃኛን በመጠቀም አዲስ መሣሪያዎችን ያጣምሩ"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"የማጣመሪያ ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
@@ -303,7 +303,7 @@
<string name="adb_warning_title" msgid="7708653449506485728">"የUSB ማረሚያ ይፈቀድ?"</string>
<string name="adb_warning_message" msgid="8145270656419669221">"የUSB አድስ ለግንባታ አላማ ብቻ የታሰበ ነው። ከኮምፒዩተርህ ወደ መሳሪያህ ውሂብ ለመገልበጥ፣ መሣሪያህ ላይ ያለ ማሳወቂያ መተግበሪያዎችን መጫን፣ እና ማስታወሻ ውሂብ ማንበብ ለመጠቀም ይቻላል።"</string>
<string name="adbwifi_warning_title" msgid="727104571653031865">"ገመድ-አልባ debugging ይፈቀድ?"</string>
- <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ debugging ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string>
+ <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ ማረም ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string>
<string name="adb_keys_warning_message" msgid="2968555274488101220">"የዩ ኤስ ቢ ማረም መዳረሻ ከዚህ ቀደም ፍቃድ ከሰጧቸው ኮምፒውተሮች ላይ ይሻሩ?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"የግንባታ ቅንብሮችን ፍቀድ?"</string>
<string name="dev_settings_warning_message" msgid="37741686486073668">"እነዚህ ቅንብሮች የታሰቡት ለግንባታ አጠቃቀም ብቻ ናቸው። መሳሪያህን እና በሱ ላይ ያሉትን መተግበሪያዎች እንዲበለሹ ወይም በትክክል እንዳይሰሩ ሊያደርጉ ይችላሉ።"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index a0bf4b1..765f31c 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (Rot-Grün-Sehschwäche)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (Blau-Gelb-Sehschwäche)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Farbkorrektur"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Hier kannst du anpassen, wie Farben auf deinem Gerät dargestellt werden sollen. Das kann in folgenden Fällen hilfreich sein:<br/><br/> <ol> <li>&nbsp;Wenn Farben genauer dargestellt werden sollen</li> <li>&nbsp;Wenn du Farben entfernen möchtest, um dich besser konzentrieren zu können</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index a18c0f1..b5da6be 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Doitu nola bistaratzen diren koloreak gailuan. Kasu hauetan izan daiteke lagungarria:<br/><br/> <ol> <li>&nbsp;Koloreak zehatzago ikusi nahi dituzunean.</li> <li>&nbsp;Hobeto fokuratzeko, koloreak kendu nahi dituzunean.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index fc788b6..a351ca0 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu-jaune)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction des couleurs"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Ajustez l\'affichage des couleurs sur votre appareil. Cette option peut vous être utile pour :<br/><br/> <ol> <li>&nbsp;Accentuer la précision des couleurs</li> <li>&nbsp;Supprimer les couleurs pour mieux vous concentrer</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 90df3b1..23baeb9 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -131,7 +131,7 @@
<string name="bluetooth_hearingaid_left_pairing_message" msgid="8561855779703533591">"מתבצעת התאמה של מכשיר שמיעה שמאלי…"</string>
<string name="bluetooth_hearingaid_right_pairing_message" msgid="2655347721696331048">"מתבצעת התאמה של מכשיר שמיעה ימני…"</string>
<string name="bluetooth_hearingaid_left_battery_level" msgid="7375621694748104876">"שמאלי - טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
- <string name="bluetooth_hearingaid_right_battery_level" msgid="1850094448499089312">"ימני - טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
+ <string name="bluetooth_hearingaid_right_battery_level" msgid="1850094448499089312">"ימני – טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
<string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi כבוי."</string>
<string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi מנותק."</string>
<string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"פס אחד של Wi-Fi."</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index e2b62a4..6c6a5d9 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ಪ್ರೊಟನೋಮಲಿ (ಕೆಂಪು-ಹಸಿರು)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ಟ್ರಿಟನೋಮಲಿ (ನೀಲಿ-ಹಳದಿ)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಬಣ್ಣಗಳು ಹೇಗೆ ಡಿಸ್ಪ್ಲೇ ಆಗುತ್ತವೆ ಎಂಬುದನ್ನು ಹೊಂದಿಸಿ. ನೀವು ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ನೋಡಲು ಬಯಸಿದಾಗ:<br/><br/> <ol> <li>&nbsp;ಇದು ಸಹಾಯಕವಾಗಿರುತ್ತದೆ</li> <li>&nbsp;ನಿಮಗೆ ಗಮನಹರಿಸಲು ಸಹಾಯ ಮಾಡಲು ಬಣ್ಣಗಳನ್ನು ತೆಗೆದುಹಾಕಿ</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 2b351bc..7ec137a 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"प्रोटानेमली (रातो, हरियो)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ट्रिटानोमेली (निलो-पंहेलो)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रङ्ग सुधार"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"तपाईंको यन्त्रमा रङहरू कस्ता देखिन्छन् भन्ने कुरा मिलाउनुहोस्। यो सुविधा निम्न अवस्थामा उपयोगी हुन सक्छ:<br/><br/> <ol> <li>&nbsp;तपाईं अझ सटीक रूपमा रङहरू देख्न चाहनुहुन्छ भने</li> <li>&nbsp;तपाईं कुनै कुरामा ध्यान केन्द्रित गर्न रङहरू हटाउन चाहनुहुन्छ भने</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index b5d064e..86b34f54 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -248,8 +248,8 @@
</string-array>
<string-array name="debug_hw_overdraw_entries">
<item msgid="1968128556747588800">"Uit"</item>
- <item msgid="3033215374382962216">"Gedeeltes met overbelasting weergeven"</item>
- <item msgid="3474333938380896988">"Gebieden voor deuteranomalie weergeven"</item>
+ <item msgid="3033215374382962216">"Gedeelten met overbelasting tonen"</item>
+ <item msgid="3474333938380896988">"Gebieden voor deuteranomalie tonen"</item>
</string-array>
<string-array name="app_process_limit_entries">
<item msgid="794656271086646068">"Standaardlimiet"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index a3a490d..e5e03e1 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -309,7 +309,7 @@
<string name="dev_settings_warning_message" msgid="37741686486073668">"Deze instellingen zijn uitsluitend bedoeld voor ontwikkelingsgebruik. Je apparaat en apps kunnen hierdoor vastlopen of anders reageren."</string>
<string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Apps verifiëren via USB"</string>
<string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Apps die zijn geïnstalleerd via ADB/ADT, controleren op schadelijk gedrag"</string>
- <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-apparaten zonder namen (alleen MAC-adressen) worden weergegeven"</string>
+ <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-apparaten zonder naam (alleen MAC-adressen) worden weergegeven"</string>
<string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Hiermee wordt de functie voor absoluut volume van Bluetooth uitgeschakeld in geval van volumeproblemen met externe apparaten, zoals een onacceptabel hoog volume of geen volumeregeling."</string>
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Hierdoor wordt de Gabeldorsche-functiestack voor bluetooth ingeschakeld."</string>
<string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Hiermee wordt de functie voor verbeterde connectiviteit ingeschakeld."</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index f1d4a8a..9ccfb80 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ਲਾਲ-ਹਰਾ)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ਨੀਲਾ-ਪੀਲਾ)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ਰੰਗ ਸੁਧਾਈ"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"ਆਪਣੇ ਡੀਵਾਈਸ \'ਤੇ ਰੰਗਾਂ ਨੂੰ ਦਿਖਾਉਣ ਦੇ ਤਰੀਕੇ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਉਦੋਂ ਲਾਹੇਵੰਦ ਹੋ ਸਕਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਇਹ ਕਰਨਾ ਚਾਹੋਗੇ:<br/><br/> <ol> <li>&nbsp;ਰੰਗਾਂ ਨੂੰ ਹੋਰ ਸਟੀਕਤਾ ਨਾਲ ਦੇਖਣਾ</li> <li>&nbsp;ਫੋਕਸ ਕਰਨ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਲਈ ਰੰਗਾਂ ਨੂੰ ਹਟਾਉਣਾ</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 95ba023..f667c6a 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (e kuqe - e gjelbër)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (e kaltër - e verdhë)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korrigjimi i ngjyrës"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Rregullo mënyrën se si shfaqen ngjyrat në pajisjen tënde. Kjo mund të jetë e dobishme kur dëshiron që:<br/><br/> <ol> <li>&nbsp;T\'i shikosh ngjyrat me më shumë saktësi</li> <li>&nbsp;T\'i heqësh ngjyrat për të të ndihmuar të fokusohesh</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 0b03d99..4c35d2f 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -424,7 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (سرخ سبز)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (نیلا پیلا)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"رنگ کی اصلاح"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"آپ کے آلے پر رنگوں کے ڈسپلے ہونے کے طریقے کو ایڈجسٹ کریں۔ یہ درج ذیل کے لیے مددگار ثابت ہوسکتا ہے :<br/>&ltlt;br/> <ol><li>جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں </li> <li>&nbsp;فوکس کرنے میں مدد کرنے کے لئے رنگوں کو ہٹائیں </li> </ol>"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"آپ کے آلے پر رنگوں کے ڈسپلے ہونے کے طریقے کو ایڈجسٹ کریں۔ یہ درج ذیل کے لیے مددگار ثابت ہوسکتا ہے :<br/>&ltlt;br/> <ol><li>جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں </li> <li>&nbsp;فوکس کرنے میں مدد کرنے کے لئے رنگوں کو ہٹانا چاہیں </li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 5d4078d..fbb84fd 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -242,7 +242,7 @@
<bool name="def_hdmiControlAutoDeviceOff">true</bool>
<!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED -->
- <bool name="def_swipe_bottom_to_notification_enabled">true</bool>
+ <bool name="def_swipe_bottom_to_notification_enabled">false</bool>
<!-- Default for Settings.Secure.ONE_HANDED_MODE_ENABLED -->
<bool name="def_one_handed_mode_enabled">false</bool>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b86ae6d..2b4fef0 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -125,7 +125,6 @@
<uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
<uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
<uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
- <uses-permission android:name="android.permission.QUERY_USERS" />
<uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
<uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
<uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
@@ -438,6 +437,9 @@
<!-- Permission required for CTS test - ResourceObserverNativeTest -->
<uses-permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
+ <!-- Permission required for CTS test - android.widget.cts.ToastTest -->
+ <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 19bcf95..6d44138c 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -48,10 +48,10 @@
<ImageView
android:id="@+id/preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0px"
+ android:layout_height="0px"
android:layout_marginBottom="42dp"
- android:layout_marginHorizontal="48dp"
+ android:paddingHorizontal="48dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/save"
@@ -64,8 +64,8 @@
<com.android.systemui.screenshot.CropView
android:id="@+id/crop_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0px"
+ android:layout_height="0px"
android:layout_marginBottom="42dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
diff --git a/packages/SystemUI/res/layout/people_space_small_view.xml b/packages/SystemUI/res/layout/people_tile_large_empty.xml
similarity index 63%
copy from packages/SystemUI/res/layout/people_space_small_view.xml
copy to packages/SystemUI/res/layout/people_tile_large_empty.xml
index 7b1abae..1e00307 100644
--- a/packages/SystemUI/res/layout/people_space_small_view.xml
+++ b/packages/SystemUI/res/layout/people_tile_large_empty.xml
@@ -20,40 +20,48 @@
<LinearLayout
android:id="@+id/item"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_gravity="center"
+ android:gravity="center"
android:background="@drawable/people_space_tile_view_card"
android:orientation="vertical"
- android:paddingHorizontal="4dp"
- android:paddingVertical="8dp">
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
<ImageView
android:id="@+id/person_icon"
android:layout_gravity="center"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
-
- <ImageView
- android:id="@+id/predefined_icon"
- android:layout_gravity="center"
- android:paddingTop="4dp"
- android:layout_width="18dp"
- android:layout_height="22dp"
- android:layout_weight="1" />
+ android:layout_height="wrap_content" />
<TextView
android:id="@+id/name"
android:layout_gravity="center"
- android:paddingTop="4dp"
+ android:paddingTop="14dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"
android:ellipsize="end"
- android:maxLines="1"
- android:paddingHorizontal="8dp"
+ android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp" />
+ android:textSize="16sp" />
+ <TextView
+ android:id="@+id/last_interaction"
+ android:layout_gravity="center"
+ android:text="@string/empty_status"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="14sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"/>
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_gravity="center"
+ android:layout_width="10dp"
+ android:layout_height="26dp"
+ android:paddingTop="16dp"
+ android:background="@drawable/circle_green_10dp"/>
</LinearLayout>
</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
new file mode 100644
index 0000000..f2341b5
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/item"
+ android:background="@drawable/people_space_tile_view_card"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:padding="16dp"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|top"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/person_icon"
+ android:layout_marginStart="-2dp"
+ android:layout_marginTop="-2dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/circle_green_10dp" />
+ </LinearLayout>
+
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="12dp"
+ android:gravity="start"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="4dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/predefined_icon"
+ android:gravity="start|center_vertical"
+ android:paddingEnd="6dp"
+ android:layout_width="24dp"
+ android:layout_height="18dp" />
+
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/subtext"
+ android:gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="12sp" />
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/people_space_content_background"
+ android:gravity="center"
+ android:scaleType="centerCrop" />
+
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/text_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:singleLine="false"
+ android:text="@string/empty_status"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
similarity index 94%
rename from packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
rename to packages/SystemUI/res/layout/people_tile_medium_empty.xml
index b1a37bf..c35787e 100644
--- a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
@@ -35,16 +35,17 @@
android:layout_height="match_parent">
<ImageView
android:id="@+id/person_icon"
- android:layout_width="60dp"
- android:layout_height="60dp"/>
+ android:layout_width="64dp"
+ android:layout_height="64dp"/>
<ImageView
android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/circle_green_10dp"/>
<LinearLayout
android:orientation="vertical"
- android:paddingStart="4dp"
+ android:paddingStart="6dp"
android:gravity="top"
android:layout_width="match_parent"
android:layout_height="wrap_content">
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
similarity index 71%
rename from packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
rename to packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index 6a606e1..e4e4cd8 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -27,28 +27,37 @@
android:layout_gravity="center"
android:padding="8dp"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="match_parent">
+
<LinearLayout
android:orientation="horizontal"
android:gravity="top"
+ android:layout_weight="1"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <ImageView
- android:gravity="start"
- android:id="@+id/person_icon"
- android:layout_width="56dp"
- android:layout_height="56dp"/>
+ android:layout_height="0dp">
+
+ <ImageView
+ android:gravity="start"
+ android:id="@+id/person_icon"
+ android:layout_marginStart="-2dp"
+ android:layout_marginTop="-2dp"
+ android:layout_width="52dp"
+ android:layout_height="52dp" />
+
<ImageView
android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
android:layout_width="10dp"
android:layout_height="10dp"
- android:background="@drawable/circle_green_10dp"/>
+ android:background="@drawable/circle_green_10dp" />
+
<LinearLayout
android:orientation="vertical"
android:gravity="top|start"
android:paddingStart="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+
<TextView
android:id="@+id/subtext"
android:text="@string/empty_user_name"
@@ -58,38 +67,36 @@
android:paddingBottom="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:ellipsize="end"/>
- <LinearLayout
- android:id="@+id/content_background"
- android:background="@drawable/people_space_content_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:singleLine="true"
+ android:ellipsize="end" />
+
<ImageView
android:id="@+id/image"
android:gravity="center"
android:background="@drawable/people_space_content_background"
android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop" />
+
+ <TextView
+ android:id="@+id/text_content"
+ android:text="@string/empty_status"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:scaleType="centerCrop"/>
- </LinearLayout>
- <TextView
- android:id="@+id/text_content"
- android:text="@string/empty_status"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="12sp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:singleLine="false"
- android:ellipsize="end"/>
+ android:maxLines="2"
+ android:singleLine="false"
+ android:ellipsize="end" />
</LinearLayout>
</LinearLayout>
+
<LinearLayout
android:gravity="bottom"
+ android:layout_gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="4dp"
- android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -101,16 +108,17 @@
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"
- android:maxLines="1"
+ android:singleLine="true"
android:ellipsize="end"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content" />
+
<ImageView
android:id="@+id/predefined_icon"
- android:gravity="end"
+ android:gravity="end|center_vertical"
android:paddingStart="6dp"
android:layout_width="24dp"
- android:layout_height="18dp"/>
+ android:layout_height="18dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_small_view.xml b/packages/SystemUI/res/layout/people_tile_small.xml
similarity index 94%
rename from packages/SystemUI/res/layout/people_space_small_view.xml
rename to packages/SystemUI/res/layout/people_tile_small.xml
index 7b1abae..914ee3c 100644
--- a/packages/SystemUI/res/layout/people_space_small_view.xml
+++ b/packages/SystemUI/res/layout/people_tile_small.xml
@@ -25,7 +25,8 @@
android:background="@drawable/people_space_tile_view_card"
android:orientation="vertical"
android:paddingHorizontal="4dp"
- android:paddingVertical="8dp">
+ android:paddingTop="6dp"
+ android:paddingBottom="8dp">
<ImageView
android:id="@+id/person_icon"
@@ -51,7 +52,7 @@
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
- android:paddingHorizontal="8dp"
+ android:paddingHorizontal="4dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0076c51..a773b22 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -202,7 +202,7 @@
<color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
<!-- Long screenshot UI -->
- <color name="screenshot_crop_scrim">#9444</color>
+ <color name="screenshot_crop_scrim">#6444</color>
<!-- GM2 colors -->
<color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b050945..ff4e8e0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -334,7 +334,6 @@
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">6dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
- <dimen name="screenshot_action_container_padding_left">96dp</dimen>
<dimen name="screenshot_action_container_padding_right">8dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
@@ -1355,6 +1354,18 @@
<dimen name="people_space_widget_radius">28dp</dimen>
<dimen name="people_space_image_radius">20dp</dimen>
<dimen name="people_space_widget_background_padding">6dp</dimen>
+ <dimen name="required_width_for_medium">146dp</dimen>
+ <dimen name="required_width_for_large">138dp</dimen>
+ <dimen name="required_height_for_large">182dp</dimen>
+ <dimen name="default_width">146dp</dimen>
+ <dimen name="default_height">92dp</dimen>
+ <dimen name="avatar_size_for_medium">52dp</dimen>
+ <dimen name="max_people_avatar_size_for_large_content">64dp</dimen>
+ <dimen name="max_people_avatar_size">108dp</dimen>
+ <dimen name="name_text_size_for_small">14sp</dimen>
+ <dimen name="name_text_size_for_large">24sp</dimen>
+ <dimen name="content_text_size_for_medium">12sp</dimen>
+ <dimen name="content_text_size_for_large">14sp</dimen>
<!-- Accessibility floating menu -->
<dimen name="accessibility_floating_menu_elevation">5dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index b1e1434..d52a251 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -22,8 +22,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.telephony.PhoneStateListener;
@@ -179,8 +179,8 @@
mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
mBgHandler.post(() -> {
- boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
- ConnectivityManager.TYPE_MOBILE);
+ boolean supported =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
if (supported && mNetworkSupported.compareAndSet(false, supported)) {
// This will set/remove the listeners appropriately. Note that it will never double
// add the listeners.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index bb4038e..b4858f4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -18,10 +18,6 @@
import static android.view.View.GONE;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.FIRST_ITEM;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.LAST_ITEM;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.REGULAR_ITEM;
-
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,16 +43,16 @@
private final List<AccessibilityTarget> mTargets;
@IntDef({
- FIRST_ITEM,
- REGULAR_ITEM,
- LAST_ITEM
+ AccessibilityTargetAdapter.FIRST_ITEM,
+ AccessibilityTargetAdapter.REGULAR_ITEM,
+ AccessibilityTargetAdapter.LAST_ITEM
})
@Retention(RetentionPolicy.SOURCE)
- @interface ItemType {
- int FIRST_ITEM = 0;
- int REGULAR_ITEM = 1;
- int LAST_ITEM = 2;
- }
+ @interface ItemType {}
+
+ private static final int FIRST_ITEM = 0;
+ private static final int REGULAR_ITEM = 1;
+ private static final int LAST_ITEM = 2;
public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
mTargets = targets;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index d3168c8..a7c1451 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -14,14 +14,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
import android.metrics.LogMaker;
import android.os.AsyncTask;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
@@ -30,12 +24,6 @@
import android.provider.Settings;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -43,7 +31,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
import com.android.systemui.assist.ui.DefaultUiController;
import com.android.systemui.dagger.SysUISingleton;
@@ -97,8 +84,6 @@
// Note that VERBOSE logging may leak PII (e.g. transcription contents).
private static final boolean VERBOSE = false;
- private static final String ASSIST_ICON_METADATA_NAME =
- "com.android.systemui.action_assist_icon";
private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
public static final String INVOCATION_TYPE_KEY = "invocation_type";
@@ -123,69 +108,29 @@
private static final long TIMEOUT_ACTIVITY = 1000;
protected final Context mContext;
- private final WindowManager mWindowManager;
private final AssistDisclosure mAssistDisclosure;
- private final InterestingConfigChanges mInterestingConfigChanges;
private final PhoneStateMonitor mPhoneStateMonitor;
private final AssistHandleBehaviorController mHandleController;
private final UiController mUiController;
protected final Lazy<SysUiState> mSysUiState;
protected final AssistLogger mAssistLogger;
- private AssistOrbContainer mView;
private final DeviceProvisionedController mDeviceProvisionedController;
private final CommandQueue mCommandQueue;
+ private final AssistOrbController mOrbController;
protected final AssistUtils mAssistUtils;
- private final boolean mShouldEnableOrb;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@Override
public void onFailed() throws RemoteException {
- mView.post(mHideRunnable);
+ mOrbController.postHide();
}
@Override
public void onShown() throws RemoteException {
- mView.post(mHideRunnable);
- }
- };
-
- private Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mView.removeCallbacks(this);
- mView.show(false /* show */, true /* animate */);
- }
- };
-
- private ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
- return;
- }
- boolean visible = false;
- if (mView != null) {
- visible = mView.isShowing();
- if (mView.isAttachedToWindow()) {
- mWindowManager.removeView(mView);
- }
- }
-
- mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
- R.layout.assist_orb, null);
- mView.setVisibility(View.GONE);
- mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
- WindowManager.LayoutParams lp = getLayoutParams();
- mWindowManager.addView(mView, lp);
- if (visible) {
- mView.show(true /* show */, false /* animate */);
- }
+ mOrbController.postHide();
}
};
@@ -205,21 +150,15 @@
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mAssistUtils = assistUtils;
mAssistDisclosure = new AssistDisclosure(context, new Handler());
mPhoneStateMonitor = phoneStateMonitor;
mHandleController = handleController;
mAssistLogger = assistLogger;
- configurationController.addCallback(mConfigurationListener);
+ mOrbController = new AssistOrbController(configurationController, context);
registerVoiceInteractionSessionListener();
- mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
- | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
- | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
- mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
- mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
mUiController = defaultUiController;
@@ -282,7 +221,7 @@
}
protected boolean shouldShowOrb() {
- return false;
+ return !ActivityManager.isLowRamDeviceStatic();
}
public void startAssist(Bundle args) {
@@ -293,10 +232,8 @@
final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) {
- showOrb(assistComponent, isService);
- mView.postDelayed(mHideRunnable, isService
- ? TIMEOUT_SERVICE
- : TIMEOUT_ACTIVITY);
+ mOrbController.showOrb(assistComponent, isService);
+ mOrbController.postHideDelayed(isService ? TIMEOUT_SERVICE : TIMEOUT_ACTIVITY);
}
if (args == null) {
@@ -340,30 +277,6 @@
mAssistUtils.hideCurrentSession();
}
- private WindowManager.LayoutParams getLayoutParams() {
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
- WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- lp.token = new Binder();
- lp.gravity = Gravity.BOTTOM | Gravity.START;
- lp.setTitle("AssistPreviewPanel");
- lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
- return lp;
- }
-
- private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
- maybeSwapSearchIcon(assistComponent, isService);
- if (mShouldEnableOrb) {
- mView.show(true /* show */, true /* animate */);
- }
- }
-
private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
boolean isService) {
if (isService) {
@@ -440,44 +353,6 @@
return mAssistUtils.isSessionRunning();
}
- private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
- replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
- isService);
- }
-
- public void replaceDrawable(ImageView v, ComponentName component, String name,
- boolean isService) {
- if (component != null) {
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = isService
- ? packageManager.getServiceInfo(
- component, PackageManager.GET_META_DATA).metaData
- : packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForApplication(
- component.getPackageName());
- v.setImageDrawable(res.getDrawable(iconResId));
- return;
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- if (VERBOSE) {
- Log.v(TAG, "Assistant component "
- + component.flattenToShortString() + " not found");
- }
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- v.setImageDrawable(null);
- }
-
protected AssistHandleBehaviorController getHandleBehaviorController() {
return mHandleController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
index f78436a..fe48c26 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
@@ -55,14 +55,17 @@
mOrb = (AssistOrbView) findViewById(R.id.assist_orb);
}
- public void show(final boolean show, boolean animate) {
+ public void show(final boolean show, boolean animate, Runnable onDone) {
if (show) {
if (getVisibility() != View.VISIBLE) {
setVisibility(View.VISIBLE);
if (animate) {
- startEnterAnimation();
+ startEnterAnimation(onDone);
} else {
reset();
+ if (onDone != null) {
+ onDone.run();
+ }
}
}
} else {
@@ -72,10 +75,16 @@
public void run() {
mAnimatingOut = false;
setVisibility(View.GONE);
+ if (onDone != null) {
+ onDone.run();
+ }
}
});
} else {
setVisibility(View.GONE);
+ if (onDone != null) {
+ onDone.run();
+ }
}
}
}
@@ -87,7 +96,7 @@
mNavbarScrim.setAlpha(1f);
}
- private void startEnterAnimation() {
+ private void startEnterAnimation(Runnable onDone) {
if (mAnimatingOut) {
return;
}
@@ -106,7 +115,8 @@
.alpha(1f)
.setDuration(300)
.setStartDelay(0)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .withEndAction(onDone);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
new file mode 100644
index 0000000..81a13a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -0,0 +1,180 @@
+/*
+ * 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.systemui.assist;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+/**
+ * AssistOrbController controls the showing and hiding of the assistant orb.
+ */
+public class AssistOrbController {
+ private static final String ASSIST_ICON_METADATA_NAME =
+ "com.android.systemui.action_assist_icon";
+ private static final String TAG = "AssistOrbController";
+ private static final boolean VERBOSE = false;
+
+ private final InterestingConfigChanges mInterestingConfigChanges;
+ private AssistOrbContainer mView;
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+
+ private Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mView.removeCallbacks(this);
+ mView.show(false /* show */, true /* animate */, () -> {
+ mWindowManager.removeView(mView);
+ });
+ }
+ };
+
+ private ConfigurationController.ConfigurationListener mConfigurationListener =
+ new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
+ return;
+ }
+ boolean visible = false;
+ if (mView != null) {
+ visible = mView.isShowing();
+ if (mView.isAttachedToWindow()) {
+ mWindowManager.removeView(mView);
+ }
+ }
+
+ if (visible) {
+ showOrb(false);
+ }
+ }
+ };
+
+ AssistOrbController(ConfigurationController configurationController, Context context) {
+ mContext = context;
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
+
+ configurationController.addCallback(mConfigurationListener);
+ mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
+ }
+
+ public void postHide() {
+ mView.post(mHideRunnable);
+ }
+
+ public void postHideDelayed(long delayMs) {
+ mView.postDelayed(mHideRunnable, delayMs);
+ }
+
+ private void showOrb(boolean animated) {
+ if (mView == null) {
+ mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
+ R.layout.assist_orb, null);
+ mView.setVisibility(View.GONE);
+ mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ }
+ if (!mView.isAttachedToWindow()) {
+ WindowManager.LayoutParams params = getLayoutParams();
+ mWindowManager.addView(mView, params);
+ }
+ mView.show(true, animated, null);
+ }
+
+ private WindowManager.LayoutParams getLayoutParams() {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.token = new Binder();
+ lp.gravity = Gravity.BOTTOM | Gravity.START;
+ lp.setTitle("AssistPreviewPanel");
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+ return lp;
+ }
+
+ public void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
+ showOrb(true);
+ maybeSwapSearchIcon(assistComponent, isService);
+ }
+
+ private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
+ replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
+ isService);
+ }
+
+ public void replaceDrawable(ImageView v, ComponentName component, String name,
+ boolean isService) {
+ if (component != null) {
+ try {
+ PackageManager packageManager = mContext.getPackageManager();
+ // Look for the search icon specified in the activity meta-data
+ Bundle metaData = isService
+ ? packageManager.getServiceInfo(
+ component, PackageManager.GET_META_DATA).metaData
+ : packageManager.getActivityInfo(
+ component, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(name);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForApplication(
+ component.getPackageName());
+ v.setImageDrawable(res.getDrawable(iconResId));
+ return;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (VERBOSE) {
+ Log.v(TAG, "Assistant component "
+ + component.flattenToShortString() + " not found");
+ }
+ } catch (Resources.NotFoundException nfe) {
+ Log.w(TAG, "Failed to swap drawable from "
+ + component.flattenToShortString(), nfe);
+ }
+ }
+ v.setImageDrawable(null);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 08e5d56..1f652db 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -33,6 +33,7 @@
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
private boolean mForceShow;
+ private boolean mQsExpanded;
protected UdfpsKeyguardViewController(
@NonNull UdfpsKeyguardView view,
@@ -64,7 +65,7 @@
protected void onViewDetached() {
super.onViewDetached();
mStatusBarStateController.removeCallback(mStateListener);
- mAlternateAuthInterceptor.reset();
+ mAlternateAuthInterceptor.resetForceShow();
mKeyguardViewManager.setAlternateAuthInterceptor(null);
}
@@ -100,6 +101,11 @@
if (mForceShow) {
return false;
}
+
+ if (mQsExpanded) {
+ return true;
+ }
+
return super.shouldPauseAuth();
}
@@ -130,7 +136,7 @@
}
@Override
- public boolean reset() {
+ public boolean resetForceShow() {
if (!mForceShow) {
return false;
}
@@ -140,7 +146,7 @@
}
@Override
- public boolean isShowingAlternativeAuth() {
+ public boolean isShowingAlternateAuth() {
return mForceShow;
}
@@ -150,6 +156,12 @@
}
@Override
+ public void setQsExpanded(boolean expanded) {
+ mQsExpanded = expanded;
+ updatePauseAuth();
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.print(getTag());
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 461a730..553e5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -50,6 +50,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -356,7 +357,8 @@
filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
- mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ mHasTelephony =
+ context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
// get notified of phone state changes
telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index f9c2a2a..93ce5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -18,42 +18,23 @@
import static android.app.Notification.CATEGORY_MISSED_CALL;
import static android.app.Notification.EXTRA_MESSAGES;
-import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
-import static android.app.people.ConversationStatus.ACTIVITY_AUDIO;
-import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_GAME;
-import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
-import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
-import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_VIDEO;
-import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
-import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.Notification;
-import android.app.PendingIntent;
import android.app.people.ConversationChannel;
-import android.app.people.ConversationStatus;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
import android.database.Cursor;
import android.database.SQLException;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.icu.text.MeasureFormat;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
@@ -66,10 +47,7 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
-import android.util.IconDrawableFactory;
import android.util.Log;
-import android.util.TypedValue;
-import android.view.View;
import android.widget.RemoteViews;
import androidx.preference.PreferenceManager;
@@ -81,8 +59,6 @@
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
import com.android.systemui.people.widget.AppWidgetOptionsHelper;
-import com.android.systemui.people.widget.LaunchConversationActivity;
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.people.widget.PeopleTileKey;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -99,10 +75,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -123,18 +96,6 @@
public static final PeopleTileKey EMPTY_KEY =
new PeopleTileKey(EMPTY_STRING, INVALID_USER_ID, EMPTY_STRING);
- public static final int SMALL_LAYOUT = 0;
- public static final int MEDIUM_LAYOUT = 1;
- @VisibleForTesting
- static final int REQUIRED_WIDTH_FOR_MEDIUM = 146;
- private static final int AVATAR_SIZE_FOR_MEDIUM = 56;
- private static final int DEFAULT_WIDTH = 146;
- private static final int DEFAULT_HEIGHT = 92;
-
- private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
- private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
- private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
- private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
/** Represents whether {@link StatusBarNotification} was posted or removed. */
public enum NotificationAction {
@@ -336,363 +297,6 @@
.build();
}
- /** Creates a {@link RemoteViews} for {@code tile}. */
- public static RemoteViews createRemoteViews(Context context,
- PeopleSpaceTile tile, int appWidgetId, Bundle options) {
- int layoutSize = getLayoutSize(context, options);
- RemoteViews viewsForTile = getViewForTile(context, tile, layoutSize);
- int maxAvatarSize = getMaxAvatarSize(context, options, layoutSize);
- RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile, maxAvatarSize);
- return setLaunchIntents(context, views, tile, appWidgetId);
- }
-
- /**
- * The prioritization for the {@code tile} content is missed calls, followed by notification
- * content, then birthdays, then the most recent status, and finally last interaction.
- */
- private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile,
- int layoutSize) {
- if (Objects.equals(tile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
- if (DEBUG) Log.d(TAG, "Create missed call view");
- return createMissedCallRemoteViews(context, tile, layoutSize);
- }
-
- if (tile.getNotificationKey() != null) {
- if (DEBUG) Log.d(TAG, "Create notification view");
- return createNotificationRemoteViews(context, tile, layoutSize);
- }
-
- // TODO: Add sorting when we expose timestamp of statuses.
- List<ConversationStatus> statusesForEntireView =
- tile.getStatuses() == null ? Arrays.asList() : tile.getStatuses().stream().filter(
- c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
- ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
- if (birthdayStatus != null) {
- if (DEBUG) Log.d(TAG, "Create birthday view");
- return createStatusRemoteViews(context, birthdayStatus, layoutSize);
- }
-
- if (!statusesForEntireView.isEmpty()) {
- if (DEBUG) {
- Log.d(TAG,
- "Create status view for: " + statusesForEntireView.get(0).getActivity());
- }
- return createStatusRemoteViews(context, statusesForEntireView.get(0), layoutSize);
- }
-
- return createLastInteractionRemoteViews(context, tile, layoutSize);
- }
-
- /** Calculates the best layout relative to the size in {@code options}. */
- private static int getLayoutSize(Context context, Bundle options) {
- int display = context.getResources().getConfiguration().orientation;
- int width = display == Configuration.ORIENTATION_PORTRAIT
- ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
- OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
- int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
- OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
- : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
- // Small layout used below a certain minimum width with any height.
- if (width < REQUIRED_WIDTH_FOR_MEDIUM) {
- if (DEBUG) Log.d(TAG, "Small view for width: " + width + " height: " + height);
- return SMALL_LAYOUT;
- }
- if (DEBUG) Log.d(TAG, "Medium view for width: " + width + " height: " + height);
- return MEDIUM_LAYOUT;
- }
-
- /** Returns the max avatar size for {@code layoutSize} under the current {@code options}. */
- private static int getMaxAvatarSize(Context context, Bundle options, int layoutSize) {
- int avatarHeightSpace = AVATAR_SIZE_FOR_MEDIUM;
- int avatarWidthSpace = AVATAR_SIZE_FOR_MEDIUM;
-
- if (layoutSize == SMALL_LAYOUT) {
- int display = context.getResources().getConfiguration().orientation;
- int width = display == Configuration.ORIENTATION_PORTRAIT
- ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
- OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
- int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
- OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
- : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
- avatarHeightSpace = height - (8 + 4 + 18 + 8);
- avatarWidthSpace = width - (4 + 4);
- }
- if (DEBUG) Log.d(TAG, "Height: " + avatarHeightSpace + " width: " + avatarWidthSpace);
- return Math.min(avatarHeightSpace, avatarWidthSpace);
- }
-
- @Nullable
- private static ConversationStatus getBirthdayStatus(PeopleSpaceTile tile,
- List<ConversationStatus> statuses) {
- Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
- c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
- if (birthdayStatus.isPresent()) {
- return birthdayStatus.get();
- }
- if (!TextUtils.isEmpty(tile.getBirthdayText())) {
- return new ConversationStatus.Builder(tile.getId(), ACTIVITY_BIRTHDAY).build();
- }
-
- return null;
- }
-
- /**
- * Returns whether a {@code status} should have its own entire templated view.
- *
- * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
- * not valid to compose an entire view.
- */
- private static boolean isStatusValidForEntireStatusView(ConversationStatus status) {
- switch (status.getActivity()) {
- // Birthday & Anniversary don't require text provided or icon provided.
- case ACTIVITY_BIRTHDAY:
- case ACTIVITY_ANNIVERSARY:
- return true;
- default:
- // For future birthday, location, new story, video, music, game, and other, the
- // app must provide either text or an icon.
- return !TextUtils.isEmpty(status.getDescription())
- || status.getIcon() != null;
- }
- }
-
- private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status,
- int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_small_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- CharSequence statusText = status.getDescription();
- if (TextUtils.isEmpty(statusText)) {
- statusText = getStatusTextByType(context, status.getActivity());
- }
- views.setViewVisibility(R.id.subtext, View.GONE);
- views.setViewVisibility(R.id.text_content, View.VISIBLE);
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
- int secondaryTextColor = context.getColor(typedValue.resourceId);
- views.setInt(R.id.text_content, "setTextColor", secondaryTextColor);
- views.setTextViewText(R.id.text_content, statusText);
- Icon statusIcon = status.getIcon();
- if (statusIcon != null) {
- views.setImageViewIcon(R.id.image, statusIcon);
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- } else {
- views.setViewVisibility(R.id.content_background, View.GONE);
- }
- // TODO: Set status pre-defined icons
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
- ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
- return views;
- }
-
- private static String getStatusTextByType(Context context, int activity) {
- switch (activity) {
- case ACTIVITY_BIRTHDAY:
- return context.getString(R.string.birthday_status);
- case ACTIVITY_UPCOMING_BIRTHDAY:
- return context.getString(R.string.upcoming_birthday_status);
- case ACTIVITY_ANNIVERSARY:
- return context.getString(R.string.anniversary_status);
- case ACTIVITY_LOCATION:
- return context.getString(R.string.location_status);
- case ACTIVITY_NEW_STORY:
- return context.getString(R.string.new_story_status);
- case ACTIVITY_VIDEO:
- return context.getString(R.string.video_status);
- case ACTIVITY_AUDIO:
- return context.getString(R.string.audio_status);
- case ACTIVITY_GAME:
- return context.getString(R.string.game_status);
- default:
- return EMPTY_STRING;
- }
- }
-
- private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
- PeopleSpaceTile tile, int maxAvatarSize) {
- try {
- boolean isAvailable =
- tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
- c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
- if (isAvailable) {
- views.setViewVisibility(R.id.availability, View.VISIBLE);
- } else {
- views.setViewVisibility(R.id.availability, View.GONE);
- }
- boolean hasNewStory =
- tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
- c -> c.getActivity() == ACTIVITY_NEW_STORY);
- views.setTextViewText(R.id.name, tile.getUserName().toString());
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- Icon icon = tile.getUserIcon();
- PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
- context.getPackageManager(),
- IconDrawableFactory.newInstance(context, false),
- maxAvatarSize);
- Drawable drawable = icon.loadDrawable(context);
- Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
- tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
- hasNewStory);
- Bitmap bitmap = convertDrawableToBitmap(personDrawable);
- views.setImageViewBitmap(R.id.person_icon, bitmap);
-
- return views;
- } catch (Exception e) {
- Log.e(TAG, "Failed to set common fields: " + e);
- }
- return views;
- }
-
- private static RemoteViews setLaunchIntents(Context context, RemoteViews views,
- PeopleSpaceTile tile, int appWidgetId) {
- try {
- Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
- activityIntent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_ACTIVITY_NO_HISTORY
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId());
- activityIntent.putExtra(
- PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName());
- activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
- tile.getUserHandle());
- activityIntent.putExtra(
- PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, tile.getNotificationKey());
- views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
- context,
- appWidgetId,
- activityIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
- return views;
- } catch (Exception e) {
- Log.e(TAG, "Failed to add launch intents: " + e);
- }
-
- return views;
- }
-
- private static RemoteViews createMissedCallRemoteViews(Context context,
- PeopleSpaceTile tile, int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_small_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- views.setViewVisibility(R.id.subtext, View.GONE);
- views.setViewVisibility(R.id.text_content, View.VISIBLE);
- views.setTextViewText(R.id.text_content, tile.getNotificationContent());
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
- ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- return views;
- }
-
- private static void ensurePredefinedIconVisibleOnSmallView(RemoteViews views, int layoutSize) {
- if (layoutSize == SMALL_LAYOUT) {
- views.setViewVisibility(R.id.name, View.GONE);
- views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
- }
- }
-
- private static RemoteViews createNotificationRemoteViews(Context context,
- PeopleSpaceTile tile, int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_small_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- if (layoutSize != MEDIUM_LAYOUT) {
- views.setViewVisibility(R.id.name, View.GONE);
- views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
- }
- Uri image = tile.getNotificationDataUri();
- ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
- if (image != null) {
- // TODO: Use NotificationInlineImageCache
- views.setImageViewUri(R.id.image, image);
- views.setViewVisibility(R.id.content_background, View.VISIBLE);
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- views.setViewVisibility(R.id.text_content, View.GONE);
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
- } else {
- CharSequence content = tile.getNotificationContent();
- views = setPunctuationRemoteViewsFields(views, content);
- views.setTextViewText(R.id.text_content, tile.getNotificationContent());
- // TODO: Measure max lines from height.
- views.setInt(R.id.text_content, "setMaxLines", 2);
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
- int primaryTextColor = context.getColor(typedValue.resourceId);
- views.setInt(R.id.text_content, "setTextColor", primaryTextColor);
- views.setViewVisibility(R.id.text_content, View.VISIBLE);
- views.setViewVisibility(R.id.content_background, View.GONE);
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
- }
- // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile.
- views.setViewVisibility(R.id.subtext, View.GONE);
- return views;
- }
-
- private static RemoteViews createLastInteractionRemoteViews(Context context,
- PeopleSpaceTile tile, int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_large_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- if (layoutSize == SMALL_LAYOUT) {
- views.setViewVisibility(R.id.name, View.VISIBLE);
- views.setViewVisibility(R.id.predefined_icon, View.GONE);
- }
- String status = PeopleSpaceUtils.getLastInteractionString(
- context, tile.getLastInteractionTimestamp());
- views.setTextViewText(R.id.last_interaction, status);
- return views;
- }
-
- private static RemoteViews setPunctuationRemoteViewsFields(
- RemoteViews views, CharSequence content) {
- String punctuation = getBackgroundTextFromMessage(content.toString());
- int visibility = View.GONE;
- if (punctuation != null) {
- visibility = View.VISIBLE;
- }
- views.setTextViewText(R.id.punctuation1, punctuation);
- views.setTextViewText(R.id.punctuation2, punctuation);
- views.setTextViewText(R.id.punctuation3, punctuation);
- views.setTextViewText(R.id.punctuation4, punctuation);
- views.setTextViewText(R.id.punctuation5, punctuation);
- views.setTextViewText(R.id.punctuation6, punctuation);
-
- views.setViewVisibility(R.id.punctuation1, visibility);
- views.setViewVisibility(R.id.punctuation2, visibility);
- views.setViewVisibility(R.id.punctuation3, visibility);
- views.setViewVisibility(R.id.punctuation4, visibility);
- views.setViewVisibility(R.id.punctuation5, visibility);
- views.setViewVisibility(R.id.punctuation6, visibility);
-
- return views;
- }
-
- /** Gets character for tile background decoration based on notification content. */
- @VisibleForTesting
- static String getBackgroundTextFromMessage(String message) {
- if (!ANY_DOUBLE_MARK_PATTERN.matcher(message).find()) {
- return null;
- }
- if (MIXED_MARK_PATTERN.matcher(message).find()) {
- return "!?";
- }
- Matcher doubleQuestionMatcher = DOUBLE_QUESTION_PATTERN.matcher(message);
- if (!doubleQuestionMatcher.find()) {
- return "!";
- }
- Matcher doubleExclamationMatcher = DOUBLE_EXCLAMATION_PATTERN.matcher(message);
- if (!doubleExclamationMatcher.find()) {
- return "?";
- }
- // If we have both "!!" and "??", return the one that comes first.
- if (doubleQuestionMatcher.start() < doubleExclamationMatcher.start()) {
- return "?";
- }
- return "!";
- }
-
/** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */
@VisibleForTesting
public static Notification.MessagingStyle.Message getLastMessagingStyleMessage(
@@ -917,7 +521,8 @@
public static void updateAppWidgetViews(AppWidgetManager appWidgetManager,
Context context, int appWidgetId, PeopleSpaceTile tile, Bundle options) {
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName());
- RemoteViews views = createRemoteViews(context, tile, appWidgetId, options);
+ RemoteViews views = new PeopleTileViewHelper(context, tile, appWidgetId,
+ options).getViews();
// Tell the AppWidgetManager to perform an update on the current app widget.
appWidgetManager.updateAppWidget(appWidgetId, views);
@@ -1005,7 +610,7 @@
if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
Bundle bundle = new Bundle();
- return PeopleSpaceUtils.createRemoteViews(context, augmentedTile, 0, bundle);
+ return new PeopleTileViewHelper(context, augmentedTile, 0, bundle).getViews();
}
/** Returns the userId associated with a {@link PeopleSpaceTile} */
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
new file mode 100644
index 0000000..ae81ab04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -0,0 +1,553 @@
+/*
+ * 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.systemui.people;
+
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_AUDIO;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_VIDEO;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+
+import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
+import static com.android.systemui.people.PeopleSpaceUtils.getUserId;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleSpaceTile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.people.widget.LaunchConversationActivity;
+import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+class PeopleTileViewHelper {
+ /** Turns on debugging information about People Space. */
+ public static final boolean DEBUG = true;
+ private static final String TAG = "PeopleTileView";
+
+ public static final int LAYOUT_SMALL = 0;
+ public static final int LAYOUT_MEDIUM = 1;
+ public static final int LAYOUT_LARGE = 2;
+
+ private static final int MIN_CONTENT_MAX_LINES = 2;
+
+ private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT = 14 + 12 + 4 + 16;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT = 8 + 4 + 4 + 8;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8;
+ private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4;
+
+ private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
+ private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
+ private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
+ private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+
+ public static final String EMPTY_STRING = "";
+
+ private Context mContext;
+ private PeopleSpaceTile mTile;
+ private float mDensity;
+ private int mAppWidgetId;
+ private int mWidth;
+ private int mHeight;
+ private int mLayoutSize;
+
+ PeopleTileViewHelper(Context context, PeopleSpaceTile tile,
+ int appWidgetId, Bundle options) {
+ mContext = context;
+ mTile = tile;
+ mAppWidgetId = appWidgetId;
+ mDensity = mContext.getResources().getDisplayMetrics().density;
+ int display = mContext.getResources().getConfiguration().orientation;
+ mWidth = display == Configuration.ORIENTATION_PORTRAIT
+ ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.default_width)) : options.getInt(
+ OPTION_APPWIDGET_MAX_WIDTH,
+ getSizeInDp(R.dimen.default_width));
+ mHeight = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
+ OPTION_APPWIDGET_MAX_HEIGHT,
+ getSizeInDp(R.dimen.default_height))
+ : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT,
+ getSizeInDp(R.dimen.default_height));
+ mLayoutSize = getLayoutSize();
+ }
+
+ public RemoteViews getViews() {
+ RemoteViews viewsForTile = getViewForTile();
+ int maxAvatarSize = getMaxAvatarSize(viewsForTile);
+ RemoteViews views = setCommonRemoteViewsFields(viewsForTile, maxAvatarSize);
+ return setLaunchIntents(views);
+ }
+
+ /**
+ * The prioritization for the {@code mTile} content is missed calls, followed by notification
+ * content, then birthdays, then the most recent status, and finally last interaction.
+ */
+ private RemoteViews getViewForTile() {
+ if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
+ if (DEBUG) Log.d(TAG, "Create missed call view");
+ return createMissedCallRemoteViews();
+ }
+
+ if (mTile.getNotificationKey() != null) {
+ if (DEBUG) Log.d(TAG, "Create notification view");
+ return createNotificationRemoteViews();
+ }
+
+ // TODO: Add sorting when we expose timestamp of statuses.
+ List<ConversationStatus> statusesForEntireView =
+ mTile.getStatuses() == null ? Arrays.asList() : mTile.getStatuses().stream().filter(
+ c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
+ ConversationStatus birthdayStatus = getBirthdayStatus(statusesForEntireView);
+ if (birthdayStatus != null) {
+ if (DEBUG) Log.d(TAG, "Create birthday view");
+ return createStatusRemoteViews(birthdayStatus);
+ }
+
+ if (!statusesForEntireView.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "Create status view for: " + statusesForEntireView.get(0).getActivity());
+ }
+ return createStatusRemoteViews(statusesForEntireView.get(0));
+ }
+
+ return createLastInteractionRemoteViews();
+ }
+
+ private void setMaxLines(RemoteViews views) {
+ int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp(
+ R.dimen.content_text_size_for_medium)
+ : getSizeInDp(R.dimen.content_text_size_for_medium);
+ int lineHeight = getLineHeight(textSize);
+ int notificationContentHeight = getContentHeightForLayout(lineHeight);
+ int maxAdaptiveLines = Math.floorDiv(notificationContentHeight, lineHeight);
+ int maxLines = Math.max(MIN_CONTENT_MAX_LINES, maxAdaptiveLines);
+ views.setInt(R.id.text_content, "setMaxLines", maxLines);
+ }
+
+ private int getLineHeight(int textSize) {
+ try {
+ TextView text = new TextView(mContext);
+ text.setTextSize(textSize);
+ int lineHeight = (int) (text.getLineHeight() / mDensity);
+ return lineHeight;
+ } catch (Exception e) {
+ Log.e(TAG, "Could not create text view: " + e);
+ return getSizeInDp(
+ R.dimen.content_text_size_for_medium);
+ }
+ }
+
+ private int getSizeInDp(int dimenResourceId) {
+ return (int) (mContext.getResources().getDimension(dimenResourceId) / mDensity);
+ }
+
+ private int getContentHeightForLayout(int lineHeight) {
+ switch (mLayoutSize) {
+ case LAYOUT_MEDIUM:
+ return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT);
+ case LAYOUT_LARGE:
+ return mHeight - (getSizeInDp(
+ R.dimen.max_people_avatar_size_for_large_content) + lineHeight
+ + FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT);
+ default:
+ return -1;
+ }
+ }
+
+ /** Calculates the best layout relative to the size in {@code options}. */
+ private int getLayoutSize() {
+ if (mHeight >= getSizeInDp(R.dimen.required_width_for_large)
+ && mWidth >= getSizeInDp(R.dimen.required_width_for_large)) {
+ if (DEBUG) Log.d(TAG, "Large view for mWidth: " + mWidth + " mHeight: " + mHeight);
+ return LAYOUT_LARGE;
+ }
+ // Small layout used below a certain minimum mWidth with any mHeight.
+ if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) {
+ if (DEBUG) Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight);
+ return LAYOUT_MEDIUM;
+ }
+ // Small layout can always handle our minimum mWidth and mHeight for our widget.
+ if (DEBUG) Log.d(TAG, "Small view for mWidth: " + mWidth + " mHeight: " + mHeight);
+ return LAYOUT_SMALL;
+ }
+
+ /** Returns the max avatar size for {@code views} under the current {@code options}. */
+ private int getMaxAvatarSize(RemoteViews views) {
+ int layoutId = views.getLayoutId();
+ int avatarSize = getSizeInDp(R.dimen.avatar_size_for_medium);
+ if (layoutId == R.layout.people_tile_medium_empty) {
+ return getSizeInDp(
+ R.dimen.max_people_avatar_size_for_large_content);
+ }
+ if (layoutId == R.layout.people_tile_medium_with_content) {
+ return getSizeInDp(R.dimen.avatar_size_for_medium);
+ }
+
+ // Calculate adaptive avatar size for remaining layouts.
+ if (layoutId == R.layout.people_tile_small) {
+ int avatarHeightSpace = mHeight - (FIXED_HEIGHT_DIMENS_FOR_SMALL + Math.max(18,
+ getLineHeight(getSizeInDp(
+ R.dimen.name_text_size_for_small))));
+ int avatarWidthSpace = mWidth - FIXED_WIDTH_DIMENS_FOR_SMALL;
+ avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
+ }
+
+ if (layoutId == R.layout.people_tile_large_with_content) {
+ avatarSize = mHeight - (FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT + (getLineHeight(
+ getSizeInDp(R.dimen.content_text_size_for_large))
+ * 3));
+ return Math.min(avatarSize, getSizeInDp(
+ R.dimen.max_people_avatar_size_for_large_content));
+ }
+
+ if (layoutId == R.layout.people_tile_large_empty) {
+ int avatarHeightSpace = mHeight - (14 + 14 + getLineHeight(
+ getSizeInDp(R.dimen.name_text_size_for_large))
+ + getLineHeight(
+ getSizeInDp(R.dimen.content_text_size_for_large))
+ + 16 + 10 + 14);
+ int avatarWidthSpace = mWidth - (14 + 14);
+ avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
+ }
+ return Math.min(avatarSize,
+ getSizeInDp(R.dimen.max_people_avatar_size));
+ }
+
+ private RemoteViews setCommonRemoteViewsFields(RemoteViews views,
+ int maxAvatarSize) {
+ try {
+ boolean isAvailable =
+ mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
+ c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
+ if (isAvailable) {
+ views.setViewVisibility(R.id.availability, View.VISIBLE);
+ } else {
+ views.setViewVisibility(R.id.availability, View.GONE);
+ }
+ boolean hasNewStory =
+ mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
+ c -> c.getActivity() == ACTIVITY_NEW_STORY);
+ views.setTextViewText(R.id.name, mTile.getUserName().toString());
+ views.setBoolean(R.id.image, "setClipToOutline", true);
+
+ Icon icon = mTile.getUserIcon();
+ PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(mContext,
+ mContext.getPackageManager(),
+ IconDrawableFactory.newInstance(mContext, false),
+ maxAvatarSize);
+ Drawable drawable = icon.loadDrawable(mContext);
+ Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
+ mTile.getPackageName(), getUserId(mTile), mTile.isImportantConversation(),
+ hasNewStory);
+ Bitmap bitmap = convertDrawableToBitmap(personDrawable);
+ views.setImageViewBitmap(R.id.person_icon, bitmap);
+
+ return views;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set common fields: " + e);
+ }
+ return views;
+ }
+
+ private RemoteViews setLaunchIntents(RemoteViews views) {
+ try {
+ Intent activityIntent = new Intent(mContext, LaunchConversationActivity.class);
+ activityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NO_HISTORY
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, mTile.getId());
+ activityIntent.putExtra(
+ PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, mTile.getPackageName());
+ activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
+ mTile.getUserHandle());
+ activityIntent.putExtra(
+ PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, mTile.getNotificationKey());
+ views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
+ mContext,
+ mAppWidgetId,
+ activityIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
+ return views;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to add launch intents: " + e);
+ }
+
+ return views;
+ }
+
+ private RemoteViews createMissedCallRemoteViews() {
+ RemoteViews views = getViewForContentLayout();
+ views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+ setMaxLines(views);
+ views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
+ return views;
+ }
+
+ private RemoteViews createNotificationRemoteViews() {
+ RemoteViews views = getViewForContentLayout();
+ Uri image = mTile.getNotificationDataUri();
+ if (image != null) {
+ // TODO: Use NotificationInlineImageCache
+ views.setImageViewUri(R.id.image, image);
+ views.setViewVisibility(R.id.image, View.VISIBLE);
+ views.setViewVisibility(R.id.text_content, View.GONE);
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
+ } else {
+ setMaxLines(views);
+ CharSequence content = mTile.getNotificationContent();
+ views = setPunctuationRemoteViewsFields(views, content);
+ TypedValue typedValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
+ int primaryTextColor = mContext.getColor(typedValue.resourceId);
+ views.setInt(R.id.text_content, "setTextColor", primaryTextColor);
+ views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
+ views.setViewVisibility(R.id.image, View.GONE);
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
+ }
+ // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and
+ // subtract 1 from maxLines when present.
+ views.setViewVisibility(R.id.subtext, View.GONE);
+ return views;
+ }
+
+ private RemoteViews createStatusRemoteViews(ConversationStatus status) {
+ RemoteViews views = getViewForContentLayout();
+ CharSequence statusText = status.getDescription();
+ if (TextUtils.isEmpty(statusText)) {
+ statusText = getStatusTextByType(status.getActivity());
+ }
+ views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+ setMaxLines(views);
+ // Secondary text color for statuses.
+ TypedValue typedValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
+ int secondaryTextColor = mContext.getColor(typedValue.resourceId);
+ views.setInt(R.id.text_content, "setTextColor", secondaryTextColor);
+ views.setTextViewText(R.id.text_content, statusText);
+
+ Icon statusIcon = status.getIcon();
+ if (statusIcon != null) {
+ // No multi-line text with status images on medium layout.
+ views.setViewVisibility(R.id.text_content, View.GONE);
+ // Show 1-line subtext on large layout with status images.
+ if (mLayoutSize == LAYOUT_LARGE) {
+ views.setViewVisibility(R.id.subtext, View.VISIBLE);
+ views.setTextViewText(R.id.subtext, statusText);
+ }
+ views.setViewVisibility(R.id.image, View.VISIBLE);
+ views.setImageViewIcon(R.id.image, statusIcon);
+ } else {
+ views.setViewVisibility(R.id.image, View.GONE);
+ }
+ // TODO: Set status pre-defined icons
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
+ return views;
+ }
+
+ @Nullable
+ private ConversationStatus getBirthdayStatus(
+ List<ConversationStatus> statuses) {
+ Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
+ c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
+ if (birthdayStatus.isPresent()) {
+ return birthdayStatus.get();
+ }
+ if (!TextUtils.isEmpty(mTile.getBirthdayText())) {
+ return new ConversationStatus.Builder(mTile.getId(), ACTIVITY_BIRTHDAY).build();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether a {@code status} should have its own entire templated view.
+ *
+ * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
+ * not valid to compose an entire view.
+ */
+ private boolean isStatusValidForEntireStatusView(ConversationStatus status) {
+ switch (status.getActivity()) {
+ // Birthday & Anniversary don't require text provided or icon provided.
+ case ACTIVITY_BIRTHDAY:
+ case ACTIVITY_ANNIVERSARY:
+ return true;
+ default:
+ // For future birthday, location, new story, video, music, game, and other, the
+ // app must provide either text or an icon.
+ return !TextUtils.isEmpty(status.getDescription())
+ || status.getIcon() != null;
+ }
+ }
+
+ private String getStatusTextByType(int activity) {
+ switch (activity) {
+ case ACTIVITY_BIRTHDAY:
+ return mContext.getString(R.string.birthday_status);
+ case ACTIVITY_UPCOMING_BIRTHDAY:
+ return mContext.getString(R.string.upcoming_birthday_status);
+ case ACTIVITY_ANNIVERSARY:
+ return mContext.getString(R.string.anniversary_status);
+ case ACTIVITY_LOCATION:
+ return mContext.getString(R.string.location_status);
+ case ACTIVITY_NEW_STORY:
+ return mContext.getString(R.string.new_story_status);
+ case ACTIVITY_VIDEO:
+ return mContext.getString(R.string.video_status);
+ case ACTIVITY_AUDIO:
+ return mContext.getString(R.string.audio_status);
+ case ACTIVITY_GAME:
+ return mContext.getString(R.string.game_status);
+ default:
+ return EMPTY_STRING;
+ }
+ }
+
+ private RemoteViews setPunctuationRemoteViewsFields(
+ RemoteViews views, CharSequence content) {
+ String punctuation = getBackgroundTextFromMessage(content.toString());
+ int visibility = View.GONE;
+ if (punctuation != null) {
+ visibility = View.VISIBLE;
+ }
+ views.setTextViewText(R.id.punctuation1, punctuation);
+ views.setTextViewText(R.id.punctuation2, punctuation);
+ views.setTextViewText(R.id.punctuation3, punctuation);
+ views.setTextViewText(R.id.punctuation4, punctuation);
+ views.setTextViewText(R.id.punctuation5, punctuation);
+ views.setTextViewText(R.id.punctuation6, punctuation);
+
+ views.setViewVisibility(R.id.punctuation1, visibility);
+ views.setViewVisibility(R.id.punctuation2, visibility);
+ views.setViewVisibility(R.id.punctuation3, visibility);
+ views.setViewVisibility(R.id.punctuation4, visibility);
+ views.setViewVisibility(R.id.punctuation5, visibility);
+ views.setViewVisibility(R.id.punctuation6, visibility);
+
+ return views;
+ }
+
+ /** Gets character for mTile background decoration based on notification content. */
+ @VisibleForTesting
+ String getBackgroundTextFromMessage(String message) {
+ if (!ANY_DOUBLE_MARK_PATTERN.matcher(message).find()) {
+ return null;
+ }
+ if (MIXED_MARK_PATTERN.matcher(message).find()) {
+ return "!?";
+ }
+ Matcher doubleQuestionMatcher = DOUBLE_QUESTION_PATTERN.matcher(message);
+ if (!doubleQuestionMatcher.find()) {
+ return "!";
+ }
+ Matcher doubleExclamationMatcher = DOUBLE_EXCLAMATION_PATTERN.matcher(message);
+ if (!doubleExclamationMatcher.find()) {
+ return "?";
+ }
+ // If we have both "!!" and "??", return the one that comes first.
+ if (doubleQuestionMatcher.start() < doubleExclamationMatcher.start()) {
+ return "?";
+ }
+ return "!";
+ }
+
+ private RemoteViews getViewForContentLayout() {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(),
+ getLayoutForContent());
+ if (mLayoutSize == LAYOUT_SMALL) {
+ views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+ views.setViewVisibility(R.id.name, View.GONE);
+ } else {
+ views.setViewVisibility(R.id.predefined_icon, View.GONE);
+ views.setViewVisibility(R.id.name, View.VISIBLE);
+ views.setViewVisibility(R.id.text_content, View.VISIBLE);
+ views.setViewVisibility(R.id.subtext, View.GONE);
+ }
+ return views;
+ }
+
+ private RemoteViews createLastInteractionRemoteViews() {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(), getEmptyLayout());
+ if (mLayoutSize == LAYOUT_SMALL) {
+ views.setViewVisibility(R.id.name, View.VISIBLE);
+ views.setViewVisibility(R.id.predefined_icon, View.GONE);
+ }
+ String status = PeopleSpaceUtils.getLastInteractionString(mContext,
+ mTile.getLastInteractionTimestamp());
+ views.setTextViewText(R.id.last_interaction, status);
+ return views;
+ }
+
+ private int getEmptyLayout() {
+ switch (mLayoutSize) {
+ case LAYOUT_MEDIUM:
+ return R.layout.people_tile_medium_empty;
+ case LAYOUT_LARGE:
+ return R.layout.people_tile_large_empty;
+ case LAYOUT_SMALL:
+ default:
+ return R.layout.people_tile_small;
+ }
+ }
+
+ private int getLayoutForContent() {
+ switch (mLayoutSize) {
+ case LAYOUT_MEDIUM:
+ return R.layout.people_tile_medium_with_content;
+ case LAYOUT_LARGE:
+ return R.layout.people_tile_large_with_content;
+ case LAYOUT_SMALL:
+ default:
+ return R.layout.people_tile_small;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index eedcdab..b1689f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -48,7 +48,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
@@ -86,7 +85,6 @@
private final View mRingerContainer;
private final QSTileHost mQSTileHost;
private final StatusBarIconController mStatusBarIconController;
- private final CommandQueue mCommandQueue;
private final DemoModeController mDemoModeController;
private final UserTracker mUserTracker;
private final StatusIconContainer mIconContainer;
@@ -204,7 +202,7 @@
PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
ActivityStarter activityStarter, UiEventLogger uiEventLogger,
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
- CommandQueue commandQueue, DemoModeController demoModeController,
+ DemoModeController demoModeController,
UserTracker userTracker, QuickQSPanelController quickQSPanelController,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
PrivacyLogger privacyLogger,
@@ -219,7 +217,6 @@
mUiEventLogger = uiEventLogger;
mQSTileHost = qsTileHost;
mStatusBarIconController = statusBarIconController;
- mCommandQueue = commandQueue;
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
@@ -238,7 +235,7 @@
mRingerContainer = mView.findViewById(R.id.ringer_container);
mIconContainer = mView.findViewById(R.id.statusIcons);
- mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, mCommandQueue);
+ mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
mColorExtractor = colorExtractor;
mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 1ec175c..78ee896 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,8 +16,6 @@
package com.android.systemui.screenshot;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
@@ -25,6 +23,7 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +31,7 @@
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
+import android.util.Range;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -49,29 +49,30 @@
*/
public class CropView extends View {
private static final String TAG = "CropView";
+
public enum CropBoundary {
- NONE, TOP, BOTTOM
+ NONE, TOP, BOTTOM, LEFT, RIGHT
}
private final float mCropTouchMargin;
private final Paint mShadePaint;
private final Paint mHandlePaint;
- // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the
- // view, respectively.
- private float mTopCrop = 0f;
- private float mBottomCrop = 1f;
-
- // When the user is dragging a handle, these variables store the distance between the top/bottom
- // crop values and
- private float mTopDelta = 0f;
- private float mBottomDelta = 0f;
+ // Crop rect with each element represented as [0,1] along its proper axis.
+ private RectF mCrop = new RectF(0, 0, 1, 1);
private int mExtraTopPadding;
private int mExtraBottomPadding;
+ private int mImageWidth;
private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
+ // The starting value of mCurrentDraggingBoundary's crop, used to compute touch deltas.
+ private float mMovementStartValue;
private float mStartingY; // y coordinate of ACTION_DOWN
+ private float mStartingX;
+ // The allowable values for the current boundary being dragged
+ private Range<Float> mMotionRange;
+
private CropInteractionListener mCropInteractionListener;
public CropView(Context context, @Nullable AttributeSet attrs) {
@@ -86,6 +87,7 @@
mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT));
mHandlePaint = new Paint();
mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK));
+ mHandlePaint.setStrokeCap(Paint.Cap.ROUND);
mHandlePaint.setStrokeWidth(
t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20));
t.recycle();
@@ -100,8 +102,7 @@
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
- ss.mTopBoundary = getTopBoundary();
- ss.mBottomBoundary = getBottomBoundary();
+ ss.mCrop = mCrop;
return ss;
}
@@ -110,45 +111,67 @@
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
- setBoundaryTo(CropBoundary.TOP, ss.mTopBoundary);
- setBoundaryTo(CropBoundary.BOTTOM, ss.mBottomBoundary);
+ mCrop = ss.mCrop;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
- float top = mTopCrop + mTopDelta;
- float bottom = mBottomCrop + mBottomDelta;
- drawShade(canvas, 0, top);
- drawShade(canvas, bottom, 1f);
- drawHandle(canvas, top, /* draw the handle tab down */ false);
- drawHandle(canvas, bottom, /* draw the handle tab up */ true);
+ drawShade(canvas, 0, 0, 1, mCrop.top);
+ drawShade(canvas, 0, mCrop.bottom, 1, 1);
+ drawShade(canvas, 0, mCrop.top, mCrop.left, mCrop.bottom);
+ drawShade(canvas, mCrop.right, mCrop.top, 1, mCrop.bottom);
+ drawHorizontalHandle(canvas, mCrop.top, /* draw the handle tab up */ true);
+ drawHorizontalHandle(canvas, mCrop.bottom, /* draw the handle tab down */ false);
+ drawVerticalHandle(canvas, mCrop.left, /* left */ true);
+ drawVerticalHandle(canvas, mCrop.right, /* right */ false);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- int topPx = fractionToPixels(mTopCrop);
- int bottomPx = fractionToPixels(mBottomCrop);
+ int topPx = fractionToVerticalPixels(mCrop.top);
+ int bottomPx = fractionToVerticalPixels(mCrop.bottom);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
- mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx);
+ mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx,
+ fractionToHorizontalPixels(mCrop.left),
+ fractionToHorizontalPixels(mCrop.right));
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
mStartingY = event.getY();
+ mStartingX = event.getX();
+ mMovementStartValue = getBoundaryPosition(mCurrentDraggingBoundary);
updateListener(event);
+ switch (mCurrentDraggingBoundary) {
+ case TOP:
+ mMotionRange = new Range<>(0f,
+ mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.BOTTOM));
+ break;
+ case BOTTOM:
+ mMotionRange = new Range<>(
+ mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.TOP), 1f);
+ break;
+ case LEFT:
+ mMotionRange = new Range<>(0f,
+ mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.RIGHT));
+ break;
+ case RIGHT:
+ mMotionRange = new Range<>(
+ mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.LEFT), 1f);
+ break;
+ }
}
return true;
case MotionEvent.ACTION_MOVE:
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
- float delta = event.getY() - mStartingY;
- if (mCurrentDraggingBoundary == CropBoundary.TOP) {
- mTopDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta,
- -topPx + mExtraTopPadding,
- bottomPx - 2 * mCropTouchMargin - topPx));
- } else { // Bottom
- mBottomDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta,
- topPx + 2 * mCropTouchMargin - bottomPx,
- getHeight() - bottomPx - mExtraBottomPadding));
- }
+ float deltaPx = isVertical(mCurrentDraggingBoundary) ? event.getY() - mStartingY
+ : event.getX() - mStartingX;
+ float delta = pixelDistanceToFraction((int) deltaPx, mCurrentDraggingBoundary);
+ setBoundaryPosition(mCurrentDraggingBoundary,
+ mMotionRange.clamp(mMovementStartValue + delta));
updateListener(event);
invalidate();
return true;
@@ -156,8 +179,6 @@
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
- // Commit the delta to the stored crop values.
- commitDeltas(mCurrentDraggingBoundary);
updateListener(event);
}
}
@@ -167,22 +188,46 @@
/**
* Set the given boundary to the given value without animation.
*/
- public void setBoundaryTo(CropBoundary boundary, float value) {
+ public void setBoundaryPosition(CropBoundary boundary, float position) {
switch (boundary) {
case TOP:
- mTopCrop = value;
+ mCrop.top = position;
break;
case BOTTOM:
- mBottomCrop = value;
+ mCrop.bottom = position;
+ break;
+ case LEFT:
+ mCrop.left = position;
+ break;
+ case RIGHT:
+ mCrop.right = position;
break;
case NONE:
- Log.w(TAG, "No boundary selected for animation");
+ Log.w(TAG, "No boundary selected");
break;
}
invalidate();
}
+ private float getBoundaryPosition(CropBoundary boundary) {
+ switch (boundary) {
+ case TOP:
+ return mCrop.top;
+ case BOTTOM:
+ return mCrop.bottom;
+ case LEFT:
+ return mCrop.left;
+ case RIGHT:
+ return mCrop.right;
+ }
+ return 0;
+ }
+
+ private static boolean isVertical(CropBoundary boundary) {
+ return boundary == CropBoundary.TOP || boundary == CropBoundary.BOTTOM;
+ }
+
/**
* Animate the given boundary to the given value.
*/
@@ -191,28 +236,13 @@
Log.w(TAG, "No boundary selected for animation");
return;
}
- float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
- : (value - mBottomCrop);
+ float start = getBoundaryPosition(boundary);
ValueAnimator animator = new ValueAnimator();
animator.addUpdateListener(animation -> {
- if (boundary == CropBoundary.TOP) {
- mTopDelta = animation.getAnimatedFraction() * totalDelta;
- } else {
- mBottomDelta = animation.getAnimatedFraction() * totalDelta;
- }
+ setBoundaryPosition(boundary,
+ MathUtils.lerp(start, value, animation.getAnimatedFraction()));
invalidate();
});
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- commitDeltas(boundary);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- commitDeltas(boundary);
- }
- });
animator.setFloatValues(0f, 1f);
animator.setDuration(750);
animator.setInterpolator(new FastOutSlowInInterpolator());
@@ -230,65 +260,79 @@
}
/**
- * @return value [0,1] representing the position of the top crop boundary. Does not reflect
- * changes from any in-progress touch input.
+ * Set the pixel width of the image on the screen (on-screen dimension, not actual bitmap
+ * dimension)
*/
- public float getTopBoundary() {
- return mTopCrop;
+ public void setImageWidth(int width) {
+ mImageWidth = width;
+ invalidate();
}
/**
- * @return value [0,1] representing the position of the bottom crop boundary. Does not reflect
- * changes from any in-progress touch input.
+ * @return RectF with values [0,1] representing the position of the boundaries along image axes.
*/
- public float getBottomBoundary() {
- return mBottomCrop;
+ public Rect getCropBoundaries(int imageWidth, int imageHeight) {
+ return new Rect((int) (mCrop.left * imageWidth), (int) (mCrop.top * imageHeight),
+ (int) (mCrop.right * imageWidth), (int) (mCrop.bottom * imageHeight));
}
public void setCropInteractionListener(CropInteractionListener listener) {
mCropInteractionListener = listener;
}
- private void commitDeltas(CropBoundary boundary) {
- if (boundary == CropBoundary.TOP) {
- mTopCrop += mTopDelta;
- mTopDelta = 0;
- } else if (boundary == CropBoundary.BOTTOM) {
- mBottomCrop += mBottomDelta;
- mBottomDelta = 0;
- }
- }
-
private void updateListener(MotionEvent event) {
- if (mCropInteractionListener != null) {
- float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
- ? mTopCrop + mTopDelta : mBottomCrop + mBottomDelta;
+ if (mCropInteractionListener != null && (isVertical(mCurrentDraggingBoundary))) {
+ float boundaryPosition = getBoundaryPosition(mCurrentDraggingBoundary);
mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary,
- boundaryPosition, fractionToPixels(boundaryPosition));
+ boundaryPosition, fractionToVerticalPixels(boundaryPosition),
+ (mCrop.left + mCrop.right) / 2);
}
}
- private void drawShade(Canvas canvas, float fracStart, float fracEnd) {
- canvas.drawRect(0, fractionToPixels(fracStart), getWidth(),
- fractionToPixels(fracEnd), mShadePaint);
+ /**
+ * Draw a shade to the given canvas with the given [0,1] fractional image bounds.
+ */
+ private void drawShade(Canvas canvas, float left, float top, float right, float bottom) {
+ canvas.drawRect(fractionToHorizontalPixels(left), fractionToVerticalPixels(top),
+ fractionToHorizontalPixels(right),
+ fractionToVerticalPixels(bottom), mShadePaint);
}
- private void drawHandle(Canvas canvas, float frac, boolean handleTabUp) {
- int y = fractionToPixels(frac);
- canvas.drawLine(0, y, getWidth(), y, mHandlePaint);
- float radius = 15 * getResources().getDisplayMetrics().density;
- float x = getWidth() * .9f;
+ private void drawHorizontalHandle(Canvas canvas, float frac, boolean handleTabUp) {
+ int y = fractionToVerticalPixels(frac);
+ canvas.drawLine(fractionToHorizontalPixels(mCrop.left), y,
+ fractionToHorizontalPixels(mCrop.right), y, mHandlePaint);
+ float radius = 8 * getResources().getDisplayMetrics().density;
+ int x = (fractionToHorizontalPixels(mCrop.left) + fractionToHorizontalPixels(mCrop.right))
+ / 2;
canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabUp ? 180 : 0, 180,
true, mHandlePaint);
}
+ private void drawVerticalHandle(Canvas canvas, float frac, boolean handleTabLeft) {
+ int x = fractionToHorizontalPixels(frac);
+ canvas.drawLine(x, fractionToVerticalPixels(mCrop.top), x,
+ fractionToVerticalPixels(mCrop.bottom), mHandlePaint);
+ float radius = 8 * getResources().getDisplayMetrics().density;
+ int y = (fractionToVerticalPixels(getBoundaryPosition(CropBoundary.TOP))
+ + fractionToVerticalPixels(
+ getBoundaryPosition(CropBoundary.BOTTOM))) / 2;
+ canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabLeft ? 90 : 270,
+ 180,
+ true, mHandlePaint);
+ }
+
/**
* Convert the given fraction position to pixel position within the View.
*/
- private int fractionToPixels(float frac) {
+ private int fractionToVerticalPixels(float frac) {
return (int) (mExtraTopPadding + frac * getImageHeight());
}
+ private int fractionToHorizontalPixels(float frac) {
+ return (int) ((getWidth() - mImageWidth) / 2 + frac * mImageWidth);
+ }
+
private int getImageHeight() {
return getHeight() - mExtraTopPadding - mExtraBottomPadding;
}
@@ -296,17 +340,30 @@
/**
* Convert the given pixel distance to fraction of the image.
*/
- private float pixelDistanceToFraction(int px) {
- return px / (float) getImageHeight();
+ private float pixelDistanceToFraction(float px, CropBoundary boundary) {
+ if (isVertical(boundary)) {
+ return px / getImageHeight();
+ } else {
+ return px / mImageWidth;
+ }
}
- private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) {
+ private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx, int leftPx,
+ int rightPx) {
if (Math.abs(event.getY() - topPx) < mCropTouchMargin) {
return CropBoundary.TOP;
}
if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) {
return CropBoundary.BOTTOM;
}
+ if (event.getY() > topPx || event.getY() < bottomPx) {
+ if (Math.abs(event.getX() - leftPx) < mCropTouchMargin) {
+ return CropBoundary.LEFT;
+ }
+ if (Math.abs(event.getX() - rightPx) < mCropTouchMargin) {
+ return CropBoundary.RIGHT;
+ }
+ }
return CropBoundary.NONE;
}
@@ -321,10 +378,10 @@
@Override
protected int getVirtualViewAt(float x, float y) {
- if (Math.abs(y - fractionToPixels(mTopCrop)) < mCropTouchMargin) {
+ if (Math.abs(y - fractionToVerticalPixels(mCrop.top)) < mCropTouchMargin) {
return TOP_HANDLE_ID;
}
- if (Math.abs(y - fractionToPixels(mBottomCrop)) < mCropTouchMargin) {
+ if (Math.abs(y - fractionToVerticalPixels(mCrop.bottom)) < mCropTouchMargin) {
return BOTTOM_HANDLE_ID;
}
return ExploreByTouchHelper.INVALID_ID;
@@ -338,7 +395,7 @@
@Override
protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
- switch(virtualViewId) {
+ switch (virtualViewId) {
case TOP_HANDLE_ID:
event.setContentDescription(
getResources().getString(R.string.screenshot_top_boundary));
@@ -353,16 +410,16 @@
@Override
protected void onPopulateNodeForVirtualView(int virtualViewId,
AccessibilityNodeInfo node) {
- switch(virtualViewId) {
+ switch (virtualViewId) {
case TOP_HANDLE_ID:
node.setContentDescription(
getResources().getString(R.string.screenshot_top_boundary));
- setNodePositions(mTopCrop, node);
+ setNodePositions(mCrop.top, node);
break;
case BOTTOM_HANDLE_ID:
node.setContentDescription(
getResources().getString(R.string.screenshot_bottom_boundary));
- setNodePositions(mBottomCrop, node);
+ setNodePositions(mCrop.bottom, node);
break;
}
@@ -380,7 +437,7 @@
}
private void setNodePositions(float fraction, AccessibilityNodeInfo node) {
- int pixels = fractionToPixels(fraction);
+ int pixels = fractionToVerticalPixels(fraction);
Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin),
getWidth(), (int) (pixels + mCropTouchMargin));
node.setBoundsInParent(rect);
@@ -400,12 +457,11 @@
* boundaries.
*/
void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition,
- int boundaryPositionPx);
+ int boundaryPositionPx, float horizontalCenter);
}
static class SavedState extends BaseSavedState {
- float mTopBoundary;
- float mBottomBoundary;
+ RectF mCrop;
/**
* Constructor called from {@link CropView#onSaveInstanceState()}
@@ -419,15 +475,13 @@
*/
private SavedState(Parcel in) {
super(in);
- mTopBoundary = in.readFloat();
- mBottomBoundary = in.readFloat();
+ mCrop = in.readParcelable(ClassLoader.getSystemClassLoader());
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
- out.writeFloat(mTopBoundary);
- out.writeFloat(mBottomBoundary);
+ out.writeParcelable(mCrop, 0);
}
public static final Parcelable.Creator<SavedState> CREATOR
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 6a004c2..01afacf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -128,7 +128,7 @@
mPreview.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
- updateCropLocation());
+ updateImageDimensions());
Intent intent = getIntent();
mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
@@ -206,7 +206,7 @@
Log.d(TAG, "onCaptureCompleted(longScreenshot=" + longScreenshot + ")");
mLongScreenshot = longScreenshot;
mPreview.setImageDrawable(mLongScreenshot.getDrawable());
- updateCropLocation();
+ updateImageDimensions();
mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
// Original boundaries go from the image tile set's y=0 to y=pageSize, so
@@ -363,10 +363,8 @@
return;
}
- Rect bounds = new Rect(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- int height = bounds.height();
- bounds.top = (int) (height * mCropView.getTopBoundary());
- bounds.bottom = (int) (height * mCropView.getBottomBoundary());
+ Rect bounds = mCropView.getCropBoundaries(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight());
if (bounds.isEmpty()) {
Log.w(TAG, "Crop bounds empty, skipping export.");
@@ -404,14 +402,16 @@
}
}
- private void updateCropLocation() {
+ private void updateImageDimensions() {
Drawable drawable = mPreview.getDrawable();
if (drawable == null) {
return;
}
Rect bounds = drawable.getBounds();
float imageRatio = bounds.width() / (float) bounds.height();
- float viewRatio = mPreview.getWidth() / (float) mPreview.getHeight();
+ int previewWidth = mPreview.getWidth() - mPreview.getPaddingLeft()
+ - mPreview.getPaddingRight();
+ float viewRatio = previewWidth / (float) mPreview.getHeight();
if (imageRatio > viewRatio) {
// Image is full width and height is constrained, compute extra padding to inform
@@ -419,9 +419,12 @@
float imageHeight = mPreview.getHeight() * viewRatio / imageRatio;
int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2;
mCropView.setExtraPadding(extraPadding, extraPadding);
+ mCropView.setImageWidth(previewWidth);
} else {
// Image is full height
mCropView.setExtraPadding(0, 0);
+ mCropView.setImageWidth((int) (mPreview.getHeight() * imageRatio));
}
+
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index 90f3042..08cd91c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -58,6 +58,7 @@
private float mCheckerboardBoxSize = 40;
private float mLastCropPosition;
+ private float mLastCenter = 0.5f;
private CropView.CropBoundary mCropBoundary;
private ViewPropertyAnimator mTranslationAnimator;
@@ -131,7 +132,7 @@
canvas.save();
// Translate such that the center of this view represents the center of the crop
// boundary.
- canvas.translate(-mDrawable.getBounds().width() / 2 + getWidth() / 2,
+ canvas.translate(-mDrawable.getBounds().width() * mLastCenter + getWidth() / 2,
-mDrawable.getBounds().height() * mLastCropPosition + getHeight() / 2);
mDrawable.draw(canvas);
canvas.restore();
@@ -148,8 +149,9 @@
@Override
public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary,
- float cropPosition, int cropPositionPx) {
+ float cropPosition, int cropPositionPx, float horizontalCenter) {
mCropBoundary = boundary;
+ mLastCenter = horizontalCenter;
boolean touchOnRight = event.getX() > getParentWidth() / 2;
float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
switch (event.getAction()) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 5c650ad..d15f1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -142,6 +142,8 @@
private ScreenshotViewCallback mCallbacks;
private Animator mDismissAnimation;
private boolean mPendingSharedTransition;
+ private GestureDetector mSwipeDetector;
+ private SwipeDismissHandler mSwipeDismissHandler;
private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
@@ -181,6 +183,23 @@
mContext.getDisplay().getRealMetrics(mDisplayMetrics);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+
+ mSwipeDetector = new GestureDetector(mContext,
+ new GestureDetector.SimpleOnGestureListener() {
+ final Rect mActionsRect = new Rect();
+
+ @Override
+ public boolean onScroll(
+ MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+ mActionsContainer.getBoundsOnScreen(mActionsRect);
+ // return true if we aren't in the actions bar, or if we are but it isn't
+ // scrollable in the direction of movement
+ return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
+ || !mActionsContainer.canScrollHorizontally((int) distanceX);
+ }
+ });
+ mSwipeDetector.setIsLongpressEnabled(false);
+ mSwipeDismissHandler = new SwipeDismissHandler();
}
/**
@@ -230,6 +249,15 @@
inoutInfo.touchableRegion.set(touchRegion);
}
+ @Override // ViewGroup
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // always pass through the down event so the swipe handler knows the initial state
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mSwipeDismissHandler.onTouch(this, ev);
+ }
+ return mSwipeDetector.onTouchEvent(ev);
+ }
+
@Override // View
protected void onFinishInflate() {
mScreenshotStatic = requireNonNull(findViewById(R.id.global_screenshot_static));
@@ -455,11 +483,7 @@
createScreenshotActionsShadeAnimation().start();
- SwipeDismissHandler swipeDismissHandler = new SwipeDismissHandler();
- mScreenshotPreview.setOnTouchListener(swipeDismissHandler);
- mActionsContainer.setOnTouchListener(swipeDismissHandler);
- mActionsContainerBackground.setOnTouchListener(swipeDismissHandler);
- mBackgroundProtection.setOnTouchListener(swipeDismissHandler);
+ setOnTouchListener(mSwipeDismissHandler);
}
});
@@ -776,30 +800,16 @@
}
class SwipeDismissHandler implements OnTouchListener {
-
- // if distance moved on ACTION_UP is less than this, register a click
- // otherwise, run return animator
- private static final float CLICK_MOVEMENT_THRESHOLD_DP = 1;
// distance needed to register a dismissal
private static final float DISMISS_DISTANCE_THRESHOLD_DP = 30;
private final GestureDetector mGestureDetector;
- private final float mDismissStartX;
- private final Rect mActionsRect = new Rect();
private float mStartX;
- private float mStartY;
- private float mTranslationX = 0;
-
- // tracks whether mStartX has been set for this interaction
- private boolean mInteractionStarted = false;
- // tracks whether we're dragging the UI (as opposed to scrolling the actions bar)
- private boolean mIsDragging = false;
SwipeDismissHandler() {
GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
mGestureDetector = new GestureDetector(mContext, gestureListener);
- mDismissStartX = mDismissButton.getX();
}
@Override
@@ -807,13 +817,9 @@
boolean gestureResult = mGestureDetector.onTouchEvent(event);
mCallbacks.onUserInteraction();
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mInteractionStarted = true;
mStartX = event.getRawX();
- mStartY = event.getRawY();
return true;
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- mInteractionStarted = false;
- mIsDragging = false;
if (isPastDismissThreshold()
&& (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
if (DEBUG_INPUT) {
@@ -821,87 +827,47 @@
}
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED);
animateDismissal(createSwipeDismissAnimation());
- return true;
- } else if (MathUtils.dist(mStartX, mStartY, event.getRawX(), event.getRawY())
- > dpToPx(CLICK_MOVEMENT_THRESHOLD_DP)) {
- // if we've moved a non-negligible distance (but not past the threshold),
+ } else {
+ // if we've moved, but not past the threshold, start the return animation
if (DEBUG_DISMISS) {
Log.d(TAG, "swipe gesture abandoned");
}
- // start the return animation
if ((mDismissAnimation == null || !mDismissAnimation.isRunning())) {
createSwipeReturnAnimation().start();
}
- return true;
- } else {
- if (view == mScreenshotPreview) {
- mScreenshotPreview.performClick();
- }
}
+ return true;
}
return gestureResult;
}
class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
-
- /**
- * This is somewhat complicated to handle because we want to allow scrolling the actions
- * bar (if it extends off the screen) as well as dismissing the UI horizontally by
- * dragging the actions bar. In addition, we don't get the initial ACTION_DOWN because
- * it is consumed by the action bar view.
- *
- * So, we use a gated system: first, keep track of the pointer location as we move;
- * next, check whether the actions bar can scroll in the direction we're moving in. If
- * it can, let the actions bar handle the event; otherwise, we've gone as far as we can
- * and can start dragging the UI instead.
- */
@Override
public boolean onScroll(
MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
- mActionsContainer.getBoundsOnScreen(mActionsRect);
- if (!mInteractionStarted) {
- if (mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())) {
- mStartX = ev2.getRawX();
- mInteractionStarted = true;
- }
- } else {
- float distance = ev2.getRawX() - mStartX;
- if ((mIsDragging && distance * mTranslationX > 0)
- || !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
- || !mActionsContainer.canScrollHorizontally(-1 * (int) distance)) {
- mIsDragging = true;
- mTranslationX = distance;
- mScreenshotStatic.setTranslationX(mTranslationX);
- return true;
- } else {
- mStartX = ev2.getRawX();
- }
- }
- return false;
+ mScreenshotStatic.setTranslationX(ev2.getRawX() - mStartX);
+ return true;
}
}
private boolean isPastDismissThreshold() {
- if (mDirectionLTR) {
- return mTranslationX <= -1 * dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
- } else {
- return mTranslationX >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
- }
+ float distance = Math.abs(mScreenshotStatic.getTranslationX());
+ return distance >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
}
private ValueAnimator createSwipeDismissAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mTranslationX;
- float finalX = mDirectionLTR
- ? -1 * (mActionsContainerBackground.getX()
- + mActionsContainerBackground.getWidth())
+ float startX = mScreenshotStatic.getTranslationX();
+ // make sure the UI gets all the way off the screen in the direction of movement
+ // (the actions container background is guaranteed to be both the leftmost and
+ // rightmost UI element in LTR and RTL)
+ float finalX = startX < 0
+ ? -1 * mActionsContainerBackground.getRight()
: mDisplayMetrics.widthPixels;
anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(startX, finalX,
- animation.getAnimatedFraction());
+ float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
mScreenshotStatic.setTranslationX(translation);
-
setAlpha(1 - animation.getAnimatedFraction());
});
anim.setDuration(400);
@@ -910,9 +876,8 @@
private ValueAnimator createSwipeReturnAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mTranslationX;
+ float startX = mScreenshotStatic.getTranslationX();
float finalX = 0;
- mTranslationX = 0;
anim.addUpdateListener(animation -> {
float translation = MathUtils.lerp(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index ca3923f..c565a271 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -38,8 +38,6 @@
import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.Properties;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
@@ -48,7 +46,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
@@ -147,23 +144,6 @@
private ImageView mBackdropFront;
private ImageView mBackdropBack;
- private boolean mShowCompactMediaSeekbar;
- private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(Properties properties) {
- for (String name : properties.getKeyset()) {
- if (SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED.equals(name)) {
- String value = properties.getString(name, null);
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: compact media seekbar flag updated: " + value);
- }
- mShowCompactMediaSeekbar = "true".equals(value);
- }
- }
- }
- };
-
private final MediaController.Callback mMediaListener = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -231,14 +211,6 @@
setupNotifPipeline();
mUsingNotifPipeline = true;
}
-
- mShowCompactMediaSeekbar = "true".equals(
- DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
-
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mContext.getMainExecutor(),
- mPropertiesChangedListener);
}
private void setupNotifPipeline() {
@@ -405,10 +377,6 @@
return mMediaMetadata;
}
- public boolean getShowCompactMediaSeekbar() {
- return mShowCompactMediaSeekbar;
- }
-
public Icon getMediaIcon() {
if (mMediaNotificationKey == null) {
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 2aba103..cc7a4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -16,7 +16,6 @@
import android.content.Context;
import android.graphics.Rect;
-import android.net.ConnectivityManager;
import android.os.Bundle;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -123,8 +122,7 @@
.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
setVisibility(showOperatorName ? VISIBLE : GONE);
- boolean hasMobile = ConnectivityManager.from(mContext)
- .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
if (!hasMobile || airplaneMode) {
setText(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
deleted file mode 100644
index f5a76f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 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.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-
-/**
- * A utility class to colorize bitmaps with a color gradient and a special blending mode
- */
-public class ImageGradientColorizer {
- public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
- int width = drawable.getIntrinsicWidth();
- int height = drawable.getIntrinsicHeight();
- int size = Math.min(width, height);
- int widthInset = (width - size) / 2;
- int heightInset = (height - size) / 2;
- drawable = drawable.mutate();
- drawable.setBounds(- widthInset, - heightInset, width - widthInset, height - heightInset);
- Bitmap newBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(newBitmap);
-
- // Values to calculate the luminance of a color
- float lr = 0.2126f;
- float lg = 0.7152f;
- float lb = 0.0722f;
-
- // Extract the red, green, blue components of the color extraction color in
- // float and int form
- int tri = Color.red(backgroundColor);
- int tgi = Color.green(backgroundColor);
- int tbi = Color.blue(backgroundColor);
-
- float tr = tri / 255f;
- float tg = tgi / 255f;
- float tb = tbi / 255f;
-
- // Calculate the luminance of the color extraction color
- float cLum = (tr * lr + tg * lg + tb * lb) * 255;
-
- ColorMatrix m = new ColorMatrix(new float[] {
- lr, lg, lb, 0, tri - cLum,
- lr, lg, lb, 0, tgi - cLum,
- lr, lg, lb, 0, tbi - cLum,
- 0, 0, 0, 1, 0,
- });
-
- Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- LinearGradient linearGradient = new LinearGradient(0, 0, size, 0,
- new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
- new float[] {0.0f, 0.4f, 1.0f}, Shader.TileMode.CLAMP);
- paint.setShader(linearGradient);
- Bitmap fadeIn = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas fadeInCanvas = new Canvas(fadeIn);
- drawable.clearColorFilter();
- drawable.draw(fadeInCanvas);
-
- if (isRtl) {
- // Let's flip the gradient
- fadeInCanvas.translate(size, 0);
- fadeInCanvas.scale(-1, 1);
- }
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
- fadeInCanvas.drawPaint(paint);
-
- Paint coloredPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- coloredPaint.setColorFilter(new ColorMatrixColorFilter(m));
- coloredPaint.setAlpha((int) (0.5f * 255));
- canvas.drawBitmap(fadeIn, 0, 0, coloredPaint);
-
- linearGradient = new LinearGradient(0, 0, size, 0,
- new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
- new float[] {0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP);
- paint.setShader(linearGradient);
- fadeInCanvas.drawPaint(paint);
- canvas.drawBitmap(fadeIn, 0, 0, null);
-
- return newBitmap;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
index 2586e94..732c115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
@@ -16,237 +16,30 @@
package com.android.systemui.statusbar.notification;
-import android.app.Notification;
-import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.util.LayoutDirection;
-import androidx.annotation.VisibleForTesting;
import androidx.palette.graphics.Palette;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.settingslib.Utils;
-
import java.util.List;
/**
- * A class the processes media notifications and extracts the right text and background colors.
+ * A gutted class that now contains only a color extraction utility used by the
+ * MediaArtworkProcessor, which has otherwise supplanted this.
+ *
+ * TODO(b/182926117): move this into MediaArtworkProcessor.kt
*/
public class MediaNotificationProcessor {
/**
- * The fraction below which we select the vibrant instead of the light/dark vibrant color
- */
- private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f;
-
- /**
- * Minimum saturation that a muted color must have if there exists if deciding between two
- * colors
- */
- private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f;
-
- /**
- * Minimum fraction that any color must have to be picked up as a text color
- */
- private static final double MINIMUM_IMAGE_FRACTION = 0.002;
-
- /**
- * The population fraction to select the dominant color as the text color over a the colored
- * ones.
- */
- private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f;
-
- /**
* The population fraction to select a white or black color as the background over a color.
*/
private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.90f;
private static final int RESIZE_BITMAP_AREA = 150 * 150;
- private final ImageGradientColorizer mColorizer;
- private final Context mContext;
- private final Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl);
- /**
- * The context of the notification. This is the app context of the package posting the
- * notification.
- */
- private final Context mPackageContext;
-
- public MediaNotificationProcessor(Context context, Context packageContext) {
- this(context, packageContext, new ImageGradientColorizer());
- }
-
- @VisibleForTesting
- MediaNotificationProcessor(Context context, Context packageContext,
- ImageGradientColorizer colorizer) {
- mContext = context;
- mPackageContext = packageContext;
- mColorizer = colorizer;
- }
-
- /**
- * Processes a builder of a media notification and calculates the appropriate colors that should
- * be used.
- *
- * @param notification the notification that is being processed
- * @param builder the recovered builder for the notification. this will be modified
- */
- public void processNotification(Notification notification, Notification.Builder builder) {
- Icon largeIcon = notification.getLargeIcon();
- Bitmap bitmap = null;
- Drawable drawable = null;
- if (largeIcon != null) {
- // We're transforming the builder, let's make sure all baked in RemoteViews are
- // rebuilt!
- builder.setRebuildStyledRemoteViews(true);
- drawable = largeIcon.loadDrawable(mPackageContext);
- int backgroundColor = 0;
- if (notification.isColorizedMedia()) {
- int width = drawable.getIntrinsicWidth();
- int height = drawable.getIntrinsicHeight();
- int area = width * height;
- if (area > RESIZE_BITMAP_AREA) {
- double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area);
- width = (int) (factor * width);
- height = (int) (factor * height);
- }
- bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, width, height);
- drawable.draw(canvas);
-
- Palette.Builder paletteBuilder = generateArtworkPaletteBuilder(bitmap);
- Palette palette = paletteBuilder.generate();
- Palette.Swatch backgroundSwatch = findBackgroundSwatch(palette);
- backgroundColor = backgroundSwatch.getRgb();
- // we want most of the full region again, slightly shifted to the right
- float textColorStartWidthFraction = 0.4f;
- paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0,
- bitmap.getWidth(),
- bitmap.getHeight());
- // We're not filtering on white or black
- if (!isWhiteOrBlack(backgroundSwatch.getHsl())) {
- final float backgroundHue = backgroundSwatch.getHsl()[0];
- paletteBuilder.addFilter((rgb, hsl) -> {
- // at least 10 degrees hue difference
- float diff = Math.abs(hsl[0] - backgroundHue);
- return diff > 10 && diff < 350;
- });
- }
- paletteBuilder.addFilter(mBlackWhiteFilter);
- palette = paletteBuilder.generate();
- int foregroundColor = selectForegroundColor(backgroundColor, palette);
- builder.setColorPalette(backgroundColor, foregroundColor);
- } else {
- backgroundColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground)
- .getDefaultColor();
- }
- Bitmap colorized = mColorizer.colorize(drawable, backgroundColor,
- mContext.getResources().getConfiguration().getLayoutDirection() ==
- LayoutDirection.RTL);
- builder.setLargeIcon(Icon.createWithBitmap(colorized));
- }
- }
-
- /**
- * Select a foreground color depending on whether the background color is dark or light
- * @param backgroundColor Background color to coordinate with
- * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
- * @return foreground color
- */
- public static int selectForegroundColor(int backgroundColor, Palette palette) {
- if (ContrastColorUtil.isColorLight(backgroundColor)) {
- return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(),
- palette.getVibrantSwatch(),
- palette.getDarkMutedSwatch(),
- palette.getMutedSwatch(),
- palette.getDominantSwatch(),
- Color.BLACK);
- } else {
- return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(),
- palette.getVibrantSwatch(),
- palette.getLightMutedSwatch(),
- palette.getMutedSwatch(),
- palette.getDominantSwatch(),
- Color.WHITE);
- }
- }
-
- private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
- Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch,
- Palette.Swatch dominantSwatch, int fallbackColor) {
- Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
- if (coloredCandidate == null) {
- coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch);
- }
- if (coloredCandidate != null) {
- if (dominantSwatch == coloredCandidate) {
- return coloredCandidate.getRgb();
- } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation()
- < POPULATION_FRACTION_FOR_DOMINANT
- && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) {
- return dominantSwatch.getRgb();
- } else {
- return coloredCandidate.getRgb();
- }
- } else if (hasEnoughPopulation(dominantSwatch)) {
- return dominantSwatch.getRgb();
- } else {
- return fallbackColor;
- }
- }
-
- private static Palette.Swatch selectMutedCandidate(Palette.Swatch first,
- Palette.Swatch second) {
- boolean firstValid = hasEnoughPopulation(first);
- boolean secondValid = hasEnoughPopulation(second);
- if (firstValid && secondValid) {
- float firstSaturation = first.getHsl()[1];
- float secondSaturation = second.getHsl()[1];
- float populationFraction = first.getPopulation() / (float) second.getPopulation();
- if (firstSaturation * populationFraction > secondSaturation) {
- return first;
- } else {
- return second;
- }
- } else if (firstValid) {
- return first;
- } else if (secondValid) {
- return second;
- }
- return null;
- }
-
- private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first,
- Palette.Swatch second) {
- boolean firstValid = hasEnoughPopulation(first);
- boolean secondValid = hasEnoughPopulation(second);
- if (firstValid && secondValid) {
- int firstPopulation = first.getPopulation();
- int secondPopulation = second.getPopulation();
- if (firstPopulation / (float) secondPopulation
- < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
- return second;
- } else {
- return first;
- }
- } else if (firstValid) {
- return first;
- } else if (secondValid) {
- return second;
- }
- return null;
- }
-
- private static boolean hasEnoughPopulation(Palette.Swatch swatch) {
- // We want a fraction that is at least 1% of the image
- return swatch != null
- && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
+ private MediaNotificationProcessor() {
}
/**
@@ -279,7 +72,7 @@
List<Palette.Swatch> swatches = palette.getSwatches();
float highestNonWhitePopulation = -1;
Palette.Swatch second = null;
- for (Palette.Swatch swatch: swatches) {
+ for (Palette.Swatch swatch : swatches) {
if (swatch != dominantSwatch
&& swatch.getPopulation() > highestNonWhitePopulation
&& !isWhiteOrBlack(swatch.getHsl())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6cf5c30..815cfb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -668,7 +668,6 @@
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
boolean isMessagingLayout = contractedView instanceof MessagingLayout;
boolean isCallLayout = contractedView instanceof CallLayout;
- boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (customView && beforeS && !mIsSummaryWithChildren) {
if (beforeN) {
@@ -678,12 +677,6 @@
} else {
smallHeight = mMaxSmallHeightBeforeS;
}
- } else if (isMediaLayout) {
- // TODO(b/172652345): MediaStyle notifications currently look broken when we enforce
- // the standard notification height, so we have to afford them more vertical space to
- // make sure we don't crop them terribly. We actually need to revisit this and give
- // them a headerless design, then remove this hack.
- smallHeight = showCompactMediaSeekbar ? mMaxSmallHeightMedia : mMaxSmallHeightBeforeS;
} else if (isMessagingLayout) {
// TODO(b/173204301): MessagingStyle notifications currently look broken when we enforce
// the standard notification height, so we have to afford them more vertical space to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 58b87cd..73c4b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -40,13 +40,11 @@
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaDataManagerKt;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -799,13 +797,6 @@
// For all of our templates, we want it to be RTL
packageContext = new RtlEnabledContext(packageContext);
}
- Notification notification = sbn.getNotification();
- if (notification.isMediaNotification() && !(mIsMediaInQS
- && MediaDataManagerKt.isMediaNotification(sbn))) {
- MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
- packageContext);
- processor.processNotification(notification, recoveredBuilder);
- }
if (mEntry.getRanking().isConversation()) {
mConversationProcessor.processNotification(mEntry, recoveredBuilder);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 2535e5d..c75cd78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -16,341 +16,26 @@
package com.android.systemui.statusbar.notification.row.wrapper;
-import static com.android.systemui.Dependency.MAIN_HANDLER;
-
-import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewStub;
-import android.widget.SeekBar;
-import android.widget.TextView;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.widget.MediaNotificationView;
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import java.util.Timer;
-import java.util.TimerTask;
-
/**
* Wraps a notification containing a media template
*/
public class NotificationMediaTemplateViewWrapper extends NotificationTemplateViewWrapper {
- private static final long PROGRESS_UPDATE_INTERVAL = 1000; // 1s
- private static final String COMPACT_MEDIA_TAG = "media";
- private final Handler mHandler = Dependency.get(MAIN_HANDLER);
- private Timer mSeekBarTimer;
private View mActions;
- private SeekBar mSeekBar;
- private TextView mSeekBarElapsedTime;
- private TextView mSeekBarTotalTime;
- private long mDuration = 0;
- private MediaController mMediaController;
- private MediaMetadata mMediaMetadata;
- private NotificationMediaManager mMediaManager;
- private View mSeekBarView;
- private Context mContext;
- private MetricsLogger mMetricsLogger;
- private boolean mIsViewVisible;
-
- @VisibleForTesting
- protected SeekBar.OnSeekBarChangeListener mSeekListener =
- new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (mMediaController != null) {
- mMediaController.getTransportControls().seekTo(mSeekBar.getProgress());
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_UPDATE));
- }
- }
- };
-
- private MediaNotificationView.VisibilityChangeListener mVisibilityListener =
- new MediaNotificationView.VisibilityChangeListener() {
- @Override
- public void onAggregatedVisibilityChanged(boolean isVisible) {
- mIsViewVisible = isVisible;
- if (isVisible && mMediaController != null) {
- // Restart timer if we're currently playing and didn't already have one going
- PlaybackState state = mMediaController.getPlaybackState();
- if (state != null && state.getState() == PlaybackState.STATE_PLAYING
- && mSeekBarTimer == null && mSeekBarView != null
- && mSeekBarView.getVisibility() != View.GONE) {
- startTimer();
- }
- } else {
- clearTimer();
- }
- }
- };
-
- private View.OnAttachStateChangeListener mAttachStateListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mIsViewVisible = false;
- }
- };
-
- private MediaController.Callback mMediaCallback = new MediaController.Callback() {
- @Override
- public void onSessionDestroyed() {
- clearTimer();
- mMediaController.unregisterCallback(this);
- if (mView instanceof MediaNotificationView) {
- ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
- mView.removeOnAttachStateChangeListener(mAttachStateListener);
- }
- }
-
- @Override
- public void onPlaybackStateChanged(@Nullable PlaybackState state) {
- if (state == null) {
- return;
- }
-
- if (state.getState() != PlaybackState.STATE_PLAYING) {
- // Update the UI once, in case playback info changed while we were paused
- updatePlaybackUi(state);
- clearTimer();
- } else if (mSeekBarTimer == null && mSeekBarView != null
- && mSeekBarView.getVisibility() != View.GONE) {
- startTimer();
- }
- }
-
- @Override
- public void onMetadataChanged(@Nullable MediaMetadata metadata) {
- if (mMediaMetadata == null || !mMediaMetadata.equals(metadata)) {
- mMediaMetadata = metadata;
- updateDuration();
- }
- }
- };
protected NotificationMediaTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
- mContext = ctx;
- mMediaManager = Dependency.get(NotificationMediaManager.class);
- mMetricsLogger = Dependency.get(MetricsLogger.class);
}
private void resolveViews() {
mActions = mView.findViewById(com.android.internal.R.id.media_actions);
- mIsViewVisible = mView.isShown();
-
- final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
- .getParcelable(Notification.EXTRA_MEDIA_SESSION);
-
- boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
- if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
- if (mSeekBarView != null) {
- mSeekBarView.setVisibility(View.GONE);
- }
- return;
- }
-
- // Check for existing media controller and clean up / create as necessary
- boolean shouldUpdateListeners = false;
- if (mMediaController == null || !mMediaController.getSessionToken().equals(token)) {
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaCallback);
- }
- mMediaController = new MediaController(mContext, token);
- shouldUpdateListeners = true;
- }
-
- mMediaMetadata = mMediaController.getMetadata();
- if (mMediaMetadata != null) {
- long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (duration <= 0) {
- // Don't include the seekbar if this is a livestream
- if (mSeekBarView != null && mSeekBarView.getVisibility() != View.GONE) {
- mSeekBarView.setVisibility(View.GONE);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
- clearTimer();
- } else if (mSeekBarView == null && shouldUpdateListeners) {
- // Only log if the controller changed, otherwise we would log multiple times for
- // the same notification when user pauses/resumes
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
- }
- return;
- } else if (mSeekBarView != null && mSeekBarView.getVisibility() == View.GONE) {
- // Otherwise, make sure the seekbar is visible
- mSeekBarView.setVisibility(View.VISIBLE);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
- updateDuration();
- startTimer();
- }
- }
-
- // Inflate the seekbar template
- ViewStub stub = mView.findViewById(R.id.notification_media_seekbar_container);
- if (stub instanceof ViewStub) {
- LayoutInflater layoutInflater = LayoutInflater.from(stub.getContext());
- stub.setLayoutInflater(layoutInflater);
- stub.setLayoutResource(R.layout.notification_material_media_seekbar);
- mSeekBarView = stub.inflate();
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
-
- mSeekBar = mSeekBarView.findViewById(R.id.notification_media_progress_bar);
- mSeekBar.setOnSeekBarChangeListener(mSeekListener);
-
- mSeekBarElapsedTime = mSeekBarView.findViewById(R.id.notification_media_elapsed_time);
- mSeekBarTotalTime = mSeekBarView.findViewById(R.id.notification_media_total_time);
-
- shouldUpdateListeners = true;
- }
-
- if (shouldUpdateListeners) {
- if (mView instanceof MediaNotificationView) {
- MediaNotificationView mediaView = (MediaNotificationView) mView;
- mediaView.addVisibilityListener(mVisibilityListener);
- mView.addOnAttachStateChangeListener(mAttachStateListener);
- }
-
- if (mSeekBarTimer == null) {
- if (mMediaController != null && canSeekMedia(mMediaController.getPlaybackState())) {
- // Log initial state, since it will not be updated
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, 1));
- } else {
- setScrubberVisible(false);
- }
- updateDuration();
- startTimer();
- mMediaController.registerCallback(mMediaCallback);
- }
- }
- updateSeekBarTint(mSeekBarView);
- }
-
- private void startTimer() {
- clearTimer();
- if (mIsViewVisible) {
- mSeekBarTimer = new Timer(true /* isDaemon */);
- mSeekBarTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- mHandler.post(mOnUpdateTimerTick);
- }
- }, 0, PROGRESS_UPDATE_INTERVAL);
- }
- }
-
- private void clearTimer() {
- if (mSeekBarTimer != null) {
- mSeekBarTimer.cancel();
- mSeekBarTimer.purge();
- mSeekBarTimer = null;
- }
- }
-
- @Override
- public void setRemoved() {
- clearTimer();
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaCallback);
- }
- if (mView instanceof MediaNotificationView) {
- ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
- mView.removeOnAttachStateChangeListener(mAttachStateListener);
- }
- }
-
- private boolean canSeekMedia(@Nullable PlaybackState state) {
- if (state == null) {
- return false;
- }
-
- long actions = state.getActions();
- return ((actions & PlaybackState.ACTION_SEEK_TO) != 0);
- }
-
- private void setScrubberVisible(boolean isVisible) {
- if (mSeekBar == null || mSeekBar.isEnabled() == isVisible) {
- return;
- }
-
- mSeekBar.getThumb().setAlpha(isVisible ? 255 : 0);
- mSeekBar.setEnabled(isVisible);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, isVisible ? 1 : 0));
- }
-
- private void updateDuration() {
- if (mMediaMetadata != null && mSeekBar != null) {
- long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (mDuration != duration) {
- mDuration = duration;
- mSeekBar.setMax((int) mDuration);
- mSeekBarTotalTime.setText(millisecondsToTimeString(duration));
- }
- }
- }
-
- protected final Runnable mOnUpdateTimerTick = new Runnable() {
- @Override
- public void run() {
- if (mMediaController != null && mSeekBar != null) {
- PlaybackState playbackState = mMediaController.getPlaybackState();
- if (playbackState != null) {
- updatePlaybackUi(playbackState);
- } else {
- clearTimer();
- }
- } else {
- clearTimer();
- }
- }
- };
-
- private void updatePlaybackUi(PlaybackState state) {
- if (mSeekBar == null || mSeekBarElapsedTime == null) {
- return;
- }
-
- long position = state.getPosition();
- mSeekBar.setProgress((int) position);
-
- mSeekBarElapsedTime.setText(millisecondsToTimeString(position));
-
- // Update scrubber in case available actions have changed
- setScrubberVisible(canSeekMedia(state));
- }
-
- private String millisecondsToTimeString(long milliseconds) {
- long seconds = milliseconds / 1000;
- String text = DateUtils.formatElapsedTime(seconds);
- return text;
}
@Override
@@ -361,28 +46,6 @@
super.onContentUpdated(row);
}
- private void updateSeekBarTint(View seekBarContainer) {
- if (seekBarContainer == null) {
- return;
- }
-
- if (this.getNotificationHeader() == null) {
- return;
- }
-
- int tintColor = getOriginalIconColor();
- mSeekBarElapsedTime.setTextColor(tintColor);
- mSeekBarTotalTime.setTextColor(tintColor);
- mSeekBarTotalTime.setShadowLayer(1.5f, 1.5f, 1.5f, mBackgroundColor);
-
- ColorStateList tintList = ColorStateList.valueOf(tintColor);
- mSeekBar.setThumbTintList(tintList);
- tintList = tintList.withAlpha(192); // 75%
- mSeekBar.setProgressTintList(tintList);
- tintList = tintList.withAlpha(128); // 50%
- mSeekBar.setProgressBackgroundTintList(tintList);
- }
-
@Override
protected void updateTransformedTypes() {
// This also clears the existing types
@@ -394,36 +57,7 @@
}
@Override
- public boolean isDimmable() {
- return getCustomBackgroundColor() == 0;
- }
-
- @Override
public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
return true;
}
-
- /**
- * Returns an initialized LogMaker for logging changes to the seekbar
- * @return new LogMaker
- */
- private LogMaker newLog(int event) {
- String packageName = mRow.getEntry().getSbn().getPackageName();
-
- return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
- .setType(event)
- .setPackageName(packageName);
- }
-
- /**
- * Returns an initialized LogMaker for logging changes with subtypes
- * @return new LogMaker
- */
- private LogMaker newLog(int event, int subtype) {
- String packageName = mRow.getEntry().getSbn().getPackageName();
- return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
- .setType(event)
- .setSubtype(subtype)
- .setPackageName(packageName);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 39f5847c..562d0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -42,6 +42,9 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -70,6 +73,8 @@
private View mOperatorNameFrame;
private CommandQueue mCommandQueue;
+ private List<String> mBlockedIcons = new ArrayList<>();
+
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setIsAirplaneMode(NetworkController.IconState icon) {
@@ -101,9 +106,12 @@
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
- mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons),
- Dependency.get(CommandQueue.class));
+ mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
mDarkIconManager.setShouldLog(true);
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_volume));
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_alarm_clock));
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_call_strength));
+ mDarkIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mClockView = mStatusBar.findViewById(R.id.clock);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 33798d6..2d760e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -47,7 +47,6 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -59,6 +58,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* The header group on Keyguard.
@@ -89,6 +90,7 @@
private int mSystemIconsBaseMargin;
private View mSystemIconsContainer;
private TintedIconManager mIconManager;
+ private List<String> mBlockedIcons = new ArrayList<>();
private View mCutoutSpace;
private ViewGroup mStatusIconArea;
@@ -121,6 +123,7 @@
mStatusIconContainer = findViewById(R.id.statusIcons);
loadDimens();
+ loadBlockList();
mBatteryController = Dependency.get(BatteryController.class);
}
@@ -181,6 +184,14 @@
R.dimen.rounded_corner_content_padding);
}
+ // Set hidden status bar items
+ private void loadBlockList() {
+ Resources r = getResources();
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_volume));
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_alarm_clock));
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_call_strength));
+ }
+
private void updateVisibilities() {
if (mMultiUserAvatar.getParent() != mStatusIconArea
&& !mKeyguardUserSwitcherEnabled) {
@@ -336,8 +347,8 @@
userInfoController.addCallback(this);
userInfoController.reloadUserInfo();
Dependency.get(ConfigurationController.class).addCallback(this);
- mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
- Dependency.get(CommandQueue.class));
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
+ mIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
onThemeChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 4ef6668..415cfff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1325,6 +1325,9 @@
traceQsJank(true /* startTracing */, false /* wasCancelled */);
flingSettings(0 /* velocity */, FLING_EXPAND);
mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0);
+ if (mAccessibilityManager.isEnabled()) {
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
}
public void expandWithoutQs() {
@@ -1824,6 +1827,7 @@
mNotificationContainerParent.setQsExpanded(expanded);
mPulseExpansionHandler.setQsExpanded(expanded);
mKeyguardBypassController.setQSExpanded(expanded);
+ mStatusBarKeyguardViewManager.setQsExpanded(expanded);
}
}
@@ -3389,7 +3393,7 @@
return new TouchHandler() {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
return true;
}
if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
@@ -3420,8 +3424,8 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
final boolean showingOrAnimatingAltAuth =
- mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating();
- if (showingOrAnimatingAltAuth) {
+ mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating();
+ if (showingOrAnimatingAltAuth && event.getAction() == MotionEvent.ACTION_DOWN) {
mStatusBarKeyguardViewManager.resetAlternateAuth();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index ed4f324..77abe79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -454,7 +454,7 @@
mView.postOnAnimation(mPostCollapseRunnable);
}
} else if (!mStatusBar.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
boolean expands = onEmptySpaceClick(mInitialTouchX);
onTrackingStopped(expands);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 0e1fe22..c83b60d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4224,7 +4224,7 @@
mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
- if (mStatusBarKeyguardViewManager.isShowingAlternativeAuth()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
} else if (mBouncerShowing) {
// Bouncer needs the front scrim when it's on top of an activity,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 8fe9a48..93b83d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
@@ -37,7 +38,6 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import java.util.ArrayList;
import java.util.List;
public interface StatusBarIconController {
@@ -54,15 +55,22 @@
* When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
* in that slot and not added as a sub slot.
*/
- public static final int TAG_PRIMARY = 0;
+ int TAG_PRIMARY = 0;
- public void addIconGroup(IconManager iconManager);
- public void removeIconGroup(IconManager iconManager);
- public void setExternalIcon(String slot);
- public void setIcon(String slot, int resourceId, CharSequence contentDescription);
- public void setIcon(String slot, StatusBarIcon icon);
- public void setSignalIcon(String slot, WifiIconState state);
- public void setMobileIcons(String slot, List<MobileIconState> states);
+ /** */
+ void addIconGroup(IconManager iconManager);
+ /** */
+ void removeIconGroup(IconManager iconManager);
+ /** */
+ void setExternalIcon(String slot);
+ /** */
+ void setIcon(String slot, int resourceId, CharSequence contentDescription);
+ /** */
+ void setIcon(String slot, StatusBarIcon icon);
+ /** */
+ void setSignalIcon(String slot, WifiIconState state);
+ /** */
+ void setMobileIcons(String slot, List<MobileIconState> states);
/**
* Display the no calling & SMS icons.
*/
@@ -85,8 +93,9 @@
* If you don't know what to pass for `tag`, either remove all icons for slot, or use
* TAG_PRIMARY to refer to the first icon at a given slot.
*/
- public void removeIcon(String slot, int tag);
- public void removeAllIconsForSlot(String slot);
+ void removeIcon(String slot, int tag);
+ /** */
+ void removeAllIconsForSlot(String slot);
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
@@ -108,12 +117,12 @@
/**
* Version of ViewGroup that observes state from the DarkIconDispatcher.
*/
- public static class DarkIconManager extends IconManager {
+ class DarkIconManager extends IconManager {
private final DarkIconDispatcher mDarkIconDispatcher;
private int mIconHPadding;
- public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) {
- super(linearLayout, commandQueue);
+ public DarkIconManager(LinearLayout linearLayout) {
+ super(linearLayout);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -169,11 +178,12 @@
}
}
- public static class TintedIconManager extends IconManager {
+ /** */
+ class TintedIconManager extends IconManager {
private int mColor;
- public TintedIconManager(ViewGroup group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ public TintedIconManager(ViewGroup group) {
+ super(group);
}
@Override
@@ -219,7 +229,9 @@
private boolean mIsInDemoMode;
protected DemoStatusIcons mDemoStatusIcons;
- public IconManager(ViewGroup group, CommandQueue commandQueue) {
+ protected ArrayList<String> mBlockList = new ArrayList<>();
+
+ public IconManager(ViewGroup group) {
mGroup = group;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -234,6 +246,15 @@
mDemoable = demoable;
}
+ public void setBlockList(@Nullable List<String> blockList) {
+ mBlockList.clear();
+ if (blockList == null || blockList.isEmpty()) {
+ return;
+ }
+
+ mBlockList.addAll(blockList);
+ }
+
public void setShouldLog(boolean should) {
mShouldLog = should;
}
@@ -249,6 +270,11 @@
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
+ // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
+ // that is in our blocked list, then we'll never see it
+ if (mBlockList.contains(slot)) {
+ blocked = true;
+ }
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 6404aea..75900a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -66,6 +66,7 @@
private Context mContext;
+ /** */
@Inject
public StatusBarIconControllerImpl(
Context context,
@@ -84,6 +85,7 @@
demoModeController.addCallback(this);
}
+ /** */
@Override
public void addIconGroup(IconManager group) {
mIconGroups.add(group);
@@ -101,12 +103,14 @@
}
}
+ /** */
@Override
public void removeIconGroup(IconManager group) {
group.destroy();
mIconGroups.remove(group);
}
+ /** */
@Override
public void onTuningChanged(String key, String newValue) {
if (!ICON_HIDE_LIST.equals(key)) {
@@ -149,6 +153,7 @@
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
}
+ /** */
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
int index = getSlotIndex(slot);
@@ -290,8 +295,9 @@
* For backwards compatibility, in the event that someone gives us a slot and a status bar icon
*/
private void setIcon(int index, StatusBarIcon icon) {
+ String slot = getSlotName(index);
if (icon == null) {
- removeAllIconsForSlot(getSlotName(index));
+ removeAllIconsForSlot(slot);
return;
}
@@ -299,6 +305,7 @@
setIcon(index, holder);
}
+ /** */
@Override
public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
boolean isNew = getIcon(index, holder.getTag()) == null;
@@ -328,6 +335,7 @@
handleSet(index, holder);
}
+ /** */
@Override
public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) {
Slot slot = getSlot(slotName);
@@ -344,15 +352,18 @@
}
}
+ /** */
public void removeIcon(String slot) {
removeAllIconsForSlot(slot);
}
+ /** */
@Override
public void removeIcon(String slot, int tag) {
removeIcon(getSlotIndex(slot), tag);
}
+ /** */
@Override
public void removeAllIconsForSlot(String slotName) {
Slot slot = getSlot(slotName);
@@ -369,6 +380,7 @@
}
}
+ /** */
@Override
public void removeIcon(int index, int tag) {
if (getIcon(index, tag) == null) {
@@ -384,6 +396,7 @@
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
+ /** */
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(TAG + " state:");
@@ -402,6 +415,7 @@
super.dump(pw);
}
+ /** */
@Override
public void onDemoModeStarted() {
for (IconManager manager : mIconGroups) {
@@ -411,6 +425,7 @@
}
}
+ /** */
@Override
public void onDemoModeFinished() {
for (IconManager manager : mIconGroups) {
@@ -420,6 +435,7 @@
}
}
+ /** */
@Override
public void dispatchDemoCommand(String command, Bundle args) {
for (IconManager manager : mIconGroups) {
@@ -429,6 +445,7 @@
}
}
+ /** */
@Override
public List<String> demoCommands() {
List<String> s = new ArrayList<>();
@@ -436,6 +453,7 @@
return s;
}
+ /** */
@Override
public void onDensityOrFontScaleChanged() {
loadDimens();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 19db02a..af342dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -39,7 +39,10 @@
private MobileIconState mMobileState;
private int mType = TYPE_ICON;
private int mTag = 0;
- private boolean mVisible = true;
+
+ private StatusBarIconHolder() {
+
+ }
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
StatusBarIconHolder wrapper = new StatusBarIconHolder();
@@ -48,7 +51,10 @@
return wrapper;
}
- public static StatusBarIconHolder fromResId(Context context, int resId,
+ /** */
+ public static StatusBarIconHolder fromResId(
+ Context context,
+ int resId,
CharSequence contentDescription) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
@@ -56,6 +62,7 @@
return holder;
}
+ /** */
public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mWifiState = state;
@@ -63,6 +70,7 @@
return holder;
}
+ /** */
public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mMobileState = state;
@@ -75,7 +83,8 @@
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
public static StatusBarIconHolder fromCallIndicatorState(
- Context context, CallIndicatorIconState state) {
+ Context context,
+ CallIndicatorIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
String contentDescription = state.isNoCalling
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c1f300b..7ee7aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -476,11 +476,12 @@
return;
}
- if (mAlternateAuthInterceptor != null
- && mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
- mStatusBar.updateScrimController();
+ if (mAlternateAuthInterceptor != null) {
mAfterKeyguardGoneAction = r;
mKeyguardGoneCancelAction = cancelAction;
+ if (mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
+ mStatusBar.updateScrimController();
+ }
return;
}
@@ -528,7 +529,7 @@
* Stop showing any alternate auth methods
*/
public void resetAlternateAuth() {
- if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.reset()) {
+ if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.resetForceShow()) {
mStatusBar.updateScrimController();
}
}
@@ -1125,18 +1126,27 @@
setDozing(isDozing);
}
+ /**
+ * Set whether qs is currently expanded
+ */
+ public void setQsExpanded(boolean expanded) {
+ if (mAlternateAuthInterceptor != null) {
+ mAlternateAuthInterceptor.setQsExpanded(expanded);
+ }
+ }
+
public KeyguardBouncer getBouncer() {
return mBouncer;
}
- public boolean isShowingAlternativeAuth() {
+ public boolean isShowingAlternateAuth() {
return mAlternateAuthInterceptor != null
- && mAlternateAuthInterceptor.isShowingAlternativeAuth();
+ && mAlternateAuthInterceptor.isShowingAlternateAuth();
}
- public boolean isShowingAlternativeAuthOrAnimating() {
+ public boolean isShowingAlternateAuthOrAnimating() {
return mAlternateAuthInterceptor != null
- && (mAlternateAuthInterceptor.isShowingAlternativeAuth()
+ && (mAlternateAuthInterceptor.isShowingAlternateAuth()
|| mAlternateAuthInterceptor.isAnimating());
}
@@ -1169,12 +1179,12 @@
* reset the state to the default (only keyguard showing, no auth methods showing)
* @return whether alternative auth method was newly hidden
*/
- boolean reset();
+ boolean resetForceShow();
/**
* @return true if alternative auth method is showing
*/
- boolean isShowingAlternativeAuth();
+ boolean isShowingAlternateAuth();
/**
* print information for the alternate auth interceptor registered
@@ -1185,5 +1195,10 @@
* @return true if the new auth method is currently animating in or out.
*/
boolean isAnimating();
+
+ /**
+ * Set whether qs is currently expanded
+ */
+ void setQsExpanded(boolean expanded);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 525f220..94edd1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -187,7 +188,7 @@
boolean removedByUser,
int reason) {
StatusBarNotificationPresenter.this.onNotificationRemoved(
- entry.getKey(), entry.getSbn());
+ entry.getKey(), entry.getSbn(), reason);
if (removedByUser) {
maybeEndAmbientPulse();
}
@@ -301,13 +302,14 @@
mNotificationPanel.updateNotificationViews(reason);
}
- public void onNotificationRemoved(String key, StatusBarNotification old) {
+ private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
&& !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE) {
+ if (mStatusBarStateController.getState() == StatusBarState.SHADE
+ && reason != NotificationListenerService.REASON_CLICK) {
mCommandQueue.animateCollapsePanels();
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 5dc9104..f2ee858 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
@@ -95,8 +94,7 @@
}
public void update() {
- boolean hasMobile = ConnectivityManager.from(mContext)
- .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index db039b4..8a86021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -79,6 +79,7 @@
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
@@ -241,8 +242,7 @@
mSubscriptionManager = subManager;
mSubDefaults = defaultsHandler;
mConnectivityManager = connectivityManager;
- mHasMobileDataFeature =
- mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ mHasMobileDataFeature = telephonyManager.isDataCapable();
mDemoModeController = demoModeController;
// telephony
@@ -337,10 +337,19 @@
// This callback is invoked a lot (i.e. when RSSI changes), so avoid updating
// icons when connectivity state has remained the same.
- if (network.equals(mLastNetwork) &&
- networkCapabilities.equalsTransportTypes(mLastNetworkCapabilities) &&
- validated == lastValidated) {
- return;
+ if (network.equals(mLastNetwork) && validated == lastValidated) {
+ // Should not rely on getTransportTypes() returning the same order of transport
+ // types. So sort the array before comparing.
+ int[] newTypes = networkCapabilities.getTransportTypes();
+ Arrays.sort(newTypes);
+
+ int[] lastTypes = (mLastNetworkCapabilities != null)
+ ? mLastNetworkCapabilities.getTransportTypes() : null;
+ if (lastTypes != null) Arrays.sort(lastTypes);
+
+ if (Arrays.equals(newTypes, lastTypes)) {
+ return;
+ }
}
mLastNetwork = network;
mLastNetworkCapabilities = networkCapabilities;
@@ -430,14 +439,13 @@
filter.addAction(Intent.ACTION_SERVICE_STATE);
filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
// Initial setup of connectivity. Handled as if we had received a sticky broadcast of
- // ConnectivityManager.CONNECTIVITY_ACTION or ConnectivityManager.INET_CONDITION_ACTION.
+ // ConnectivityManager.CONNECTIVITY_ACTION.
mReceiverHandler.post(this::updateConnectivity);
// Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast
@@ -682,7 +690,6 @@
final String action = intent.getAction();
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
- case ConnectivityManager.INET_CONDITION_ACTION:
updateConnectivity();
break;
case Intent.ACTION_AIRPLANE_MODE_CHANGED:
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index bbb2f1a..278663b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -65,8 +65,6 @@
"android.theme.customization.accent_color";
static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
"android.theme.customization.system_palette";
- static final String OVERLAY_CATEGORY_NEUTRAL_PALETTE =
- "android.theme.customization.neutral_palette";
@VisibleForTesting
static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
@VisibleForTesting
@@ -94,7 +92,6 @@
*/
static final List<String> THEME_CATEGORIES = Lists.newArrayList(
OVERLAY_CATEGORY_SYSTEM_PALETTE,
- OVERLAY_CATEGORY_NEUTRAL_PALETTE,
OVERLAY_CATEGORY_ICON_LAUNCHER,
OVERLAY_CATEGORY_SHAPE,
OVERLAY_CATEGORY_FONT,
@@ -108,7 +105,6 @@
@VisibleForTesting
static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
OVERLAY_CATEGORY_SYSTEM_PALETTE,
- OVERLAY_CATEGORY_NEUTRAL_PALETTE,
OVERLAY_CATEGORY_ACCENT_COLOR,
OVERLAY_CATEGORY_FONT,
OVERLAY_CATEGORY_SHAPE,
@@ -131,8 +127,8 @@
mLauncherPackage = launcherPackage;
mThemePickerPackage = themePickerPackage;
mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
- OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_NEUTRAL_PALETTE,
- OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
+ OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR,
+ OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
OVERLAY_CATEGORY_ICON_ANDROID));
mTargetPackageToCategories.put(SYSUI_PACKAGE,
Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI));
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f192287..d317712 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -16,7 +16,6 @@
package com.android.systemui.theme;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import android.annotation.Nullable;
@@ -82,9 +81,8 @@
protected static final String TAG = "ThemeOverlayController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected static final int PRIMARY = 0;
- protected static final int SECONDARY = 1;
- protected static final int NEUTRAL = 2;
+ protected static final int NEUTRAL = 0;
+ protected static final int ACCENT = 1;
private final ThemeOverlayApplier mThemeManager;
private final UserManager mUserManager;
@@ -103,8 +101,6 @@
protected int mMainWallpaperColor = Color.TRANSPARENT;
// Accent color extracted from wallpaper, NOT the color used on the overlay
protected int mWallpaperAccentColor = Color.TRANSPARENT;
- // System colors overlay
- private FabricatedOverlay mPrimaryOverlay;
// Accent colors overlay
private FabricatedOverlay mSecondaryOverlay;
// Neutral system colors overlay
@@ -205,7 +201,7 @@
mainColor = Color.TRANSPARENT;
accentCandidate = Color.TRANSPARENT;
} else {
- mainColor = getDominantColor(currentColors);
+ mainColor = getNeutralColor(currentColors);
accentCandidate = getAccentColor(currentColors);
}
@@ -218,13 +214,12 @@
mWallpaperAccentColor = accentCandidate;
if (mIsMonetEnabled) {
- mPrimaryOverlay = getOverlay(mMainWallpaperColor, PRIMARY);
- mSecondaryOverlay = getOverlay(mWallpaperAccentColor, SECONDARY);
+ mSecondaryOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL);
mNeedsOverlayCreation = true;
if (DEBUG) {
- Log.d(TAG, "fetched overlays. primary: " + mPrimaryOverlay + " secondary: "
- + mSecondaryOverlay + " neutral: " + mNeutralOverlay);
+ Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay
+ + " neutral: " + mNeutralOverlay);
}
}
@@ -234,7 +229,7 @@
/**
* Return the main theme color from a given {@link WallpaperColors} instance.
*/
- protected int getDominantColor(@NonNull WallpaperColors wallpaperColors) {
+ protected int getNeutralColor(@NonNull WallpaperColors wallpaperColors) {
return wallpaperColors.getPrimaryColor().toArgb();
}
@@ -283,8 +278,6 @@
if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
try {
int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
- mPrimaryOverlay = getOverlay(color, PRIMARY);
- // Neutral palette is always derived from primary color.
mNeutralOverlay = getOverlay(color, NEUTRAL);
mNeedsOverlayCreation = true;
categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
@@ -308,7 +301,7 @@
if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
try {
int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
- mSecondaryOverlay = getOverlay(color, SECONDARY);
+ mSecondaryOverlay = getOverlay(color, ACCENT);
mNeedsOverlayCreation = true;
categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
} catch (NumberFormatException e) {
@@ -326,9 +319,8 @@
// Compatibility with legacy themes, where full packages were defined, instead of just
// colors.
if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
- && mPrimaryOverlay != null) {
- categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mPrimaryOverlay.getIdentifier());
- categoryToPackage.put(OVERLAY_CATEGORY_NEUTRAL_PALETTE,
+ && mNeutralOverlay != null) {
+ categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
mNeutralOverlay.getIdentifier());
}
if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
@@ -350,7 +342,7 @@
if (mNeedsOverlayCreation) {
mNeedsOverlayCreation = false;
mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
- mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay
+ mSecondaryOverlay, mNeutralOverlay
}, currentUser, managedProfiles);
} else {
mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
@@ -363,7 +355,6 @@
pw.println("mSystemColors=" + mSystemColors);
pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
- pw.println("mPrimaryOverlay=" + mPrimaryOverlay);
pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
pw.println("mNeutralOverlay=" + mNeutralOverlay);
pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index aa4122f..6f2c0af 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -37,7 +37,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.net.ConnectivityManager;
+import android.content.pm.PackageManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
@@ -99,7 +99,7 @@
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- private ConnectivityManager mConnectivityManager;
+ private PackageManager mPackageManager;
@Mock
private TelephonyManager mTelephonyManager;
@Mock
@@ -123,8 +123,8 @@
mTestableLooper = TestableLooper.get(this);
mContext.addMockSystemService(WifiManager.class, mWifiManager);
- mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
- when(mConnectivityManager.isNetworkSupported(anyInt())).thenReturn(true);
+ mContext.addMockSystemService(PackageManager.class, mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
mContext.getOrCreateTestableResources().addOverride(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 65f0f7b..4410992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -170,11 +170,11 @@
mAltAuthInterceptor.showAlternativeAuthMethod(); // force show
assertFalse(mController.shouldPauseAuth());
- assertTrue(mAltAuthInterceptor.isShowingAlternativeAuth());
+ assertTrue(mAltAuthInterceptor.isShowingAlternateAuth());
- mAltAuthInterceptor.reset(); // stop force show
+ mAltAuthInterceptor.resetForceShow(); // stop force show
assertTrue(mController.shouldPauseAuth());
- assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+ assertFalse(mAltAuthInterceptor.isShowingAlternateAuth());
}
@Test
@@ -190,7 +190,7 @@
mController.onViewDetached();
// THEN alt auth state reports not showing
- assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+ assertFalse(mAltAuthInterceptor.isShowingAlternateAuth());
}
private void sendStatusBarStateChanged(int statusBarState) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index ee98a59..1c7a84a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -16,20 +16,11 @@
package com.android.systemui.people;
-import static android.app.Notification.CATEGORY_MISSED_CALL;
-import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_GAME;
-import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
-import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
-
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
-import static com.android.systemui.people.PeopleSpaceUtils.REQUIRED_WIDTH_FOR_MEDIUM;
import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -48,7 +39,6 @@
import android.app.NotificationManager;
import android.app.Person;
import android.app.people.ConversationChannel;
-import android.app.people.ConversationStatus;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
@@ -70,9 +60,7 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.TextView;
+import android.util.DisplayMetrics;
import androidx.test.filters.SmallTest;
@@ -117,8 +105,6 @@
private static final int TEST_COLUMN_INDEX = 1;
private static final Uri URI = Uri.parse("fake_uri");
private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
- private static final String GAME_DESCRIPTION = "Playing a game!";
- private static final CharSequence MISSED_CALL = "Custom missed call message";
private static final String NAME = "username";
private static final Person PERSON = new Person.Builder()
.setName("name")
@@ -126,11 +112,6 @@
.setUri(URI.toString())
.setBot(false)
.build();
- private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
- new PeopleSpaceTile
- .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
- .setLastInteractionTimestamp(0L)
- .build();
private static final PeopleSpaceTile PERSON_TILE =
new PeopleSpaceTile
.Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
@@ -139,16 +120,6 @@
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
.build();
- private static final ConversationStatus GAME_STATUS =
- new ConversationStatus
- .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
- .setDescription(GAME_DESCRIPTION)
- .build();
- private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
- new ConversationStatus
- .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
- .setAvailability(AVAILABILITY_AVAILABLE)
- .build();
private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
SHORTCUT_ID_1).setLongLabel(
@@ -244,6 +215,12 @@
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
.thenReturn(new Bundle());
+ Configuration configuration = mock(Configuration.class);
+ DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
+ Resources resources = mock(Resources.class);
+ when(mMockContext.getResources()).thenReturn(resources);
+ when(resources.getConfiguration()).thenReturn(configuration);
+ when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
when(mMockContentResolver.query(any(Uri.class), any(), anyString(), any(),
isNull())).thenReturn(mMockCursor);
@@ -254,10 +231,6 @@
when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
mContext.getString(R.string.over_timestamp));
- Configuration configuration = mock(Configuration.class);
- Resources resources = mock(Resources.class);
- when(mMockContext.getResources()).thenReturn(resources);
- when(resources.getConfiguration()).thenReturn(configuration);
when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
when(mNotificationEntryManager.getVisibleNotifications())
.thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
@@ -362,95 +335,6 @@
}
@Test
- public void testGetBackgroundTextFromMessageNoPunctuation() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageSingleExclamation() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test!");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageSingleQuestion() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("?test");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageSeparatedMarks() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test! right!");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageDoubleExclamation() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("!!test");
-
- assertThat(backgroundText).isEqualTo("!");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageDoubleQuestion() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test??");
-
- assertThat(backgroundText).isEqualTo("?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMixed() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test?!");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMixedInTheMiddle() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!? in the middle");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMixedDifferentOrder() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!? in the middle");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMultiple() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!?!!? in the middle");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageQuestionFirst() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test?? in the middle!!");
-
- assertThat(backgroundText).isEqualTo("?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageExclamationFirst() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!! in the middle??");
-
- assertThat(backgroundText).isEqualTo("!");
- }
-
- @Test
public void testGetLastMessagingStyleMessage() {
StatusBarNotification sbn = new SbnBuilder()
.setNotification(mNotification1)
@@ -687,225 +571,6 @@
any());
}
- @Test
- public void testCreateRemoteViewsWithLastInteractionTime() {
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
- PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has last interaction.
- TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
- assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
- // No availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.GONE, availability.getVisibility());
- // Shows person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // No status.
- assertThat((View) result.findViewById(R.id.text_content)).isNull();
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show name over predefined icon.
- assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Shows person icon.
- assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
- PeopleSpaceTile tileWithAvailabilityAndNewStory =
- PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
- Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
- new ConversationStatus.Builder(
- PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
- ACTIVITY_GAME).build())).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
- tileWithAvailabilityAndNewStory, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has last interaction over status.
- TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
- assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // No status.
- assertThat((View) result.findViewById(R.id.text_content)).isNull();
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithAvailabilityAndNewStory, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show name rather than game type.
- assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
- PeopleSpaceTile tileWithStatusTemplate =
- PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
- Arrays.asList(
- NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
- PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
- ACTIVITY_BIRTHDAY).build())).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has status text from backup text.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithStatusTemplate() {
- PeopleSpaceTile tileWithStatusTemplate =
- PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
- Arrays.asList(GAME_STATUS,
- NEW_STORY_WITH_AVAILABILITY)).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has status.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), GAME_DESCRIPTION);
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithMissedCallNotification() {
- PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
- .setNotificationDataUri(null)
- .setNotificationCategory(CATEGORY_MISSED_CALL)
- .setNotificationContent(MISSED_CALL)
- .build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithMissedCallNotification, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.GONE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has missed call notification content.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), MISSED_CALL);
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithMissedCallNotification, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithNotificationTemplate() {
- PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
- .setNotificationDataUri(null)
- .setStatuses(Arrays.asList(GAME_STATUS,
- NEW_STORY_WITH_AVAILABILITY)).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusAndNotification, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- TextView subtext = (TextView) result.findViewById(R.id.subtext);
- assertEquals(View.GONE, subtext.getVisibility());
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has notification content.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusAndNotification, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
boolean importantConversation, long lastInteractionTimestamp) throws Exception {
ConversationChannelWrapper convo = new ConversationChannelWrapper();
@@ -931,13 +596,4 @@
eq(shortcutId))).thenReturn(lastInteractionTimestamp);
return convo;
}
-
- private ConversationChannel getConversationChannelWithoutTimestamp(String shortcutId)
- throws Exception {
- ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
- "name").build();
- ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
- 0L, false);
- return convo;
- }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
new file mode 100644
index 0000000..39bf060
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -0,0 +1,587 @@
+/*
+ * 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.systemui.people;
+
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+
+import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleSpaceTile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+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.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PeopleTileViewHelperTest extends SysuiTestCase {
+
+ private static final String SHORTCUT_ID_1 = "101";
+ private static final String NOTIFICATION_KEY = "notification_key";
+ private static final String NOTIFICATION_CONTENT = "notification_content";
+ private static final Uri URI = Uri.parse("fake_uri");
+ private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
+ private static final String GAME_DESCRIPTION = "Playing a game!";
+ private static final CharSequence MISSED_CALL = "Custom missed call message";
+ private static final String NAME = "username";
+ private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+ .setLastInteractionTimestamp(0L)
+ .build();
+ private static final PeopleSpaceTile PERSON_TILE =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+ .setLastInteractionTimestamp(123L)
+ .setNotificationKey(NOTIFICATION_KEY)
+ .setNotificationContent(NOTIFICATION_CONTENT)
+ .setNotificationDataUri(URI)
+ .build();
+ private static final ConversationStatus GAME_STATUS =
+ new ConversationStatus
+ .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
+ .setDescription(GAME_DESCRIPTION)
+ .build();
+ private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
+ new ConversationStatus
+ .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
+
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private Bundle mOptions;
+ private PeopleTileViewHelper mPeopleTileViewHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mOptions = new Bundle();
+ mOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
+
+ when(mMockContext.getString(R.string.birthday_status)).thenReturn(
+ mContext.getString(R.string.birthday_status));
+ when(mMockContext.getString(R.string.basic_status)).thenReturn(
+ mContext.getString(R.string.basic_status));
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
+ mContext.getString(R.string.over_timestamp));
+ Configuration configuration = mock(Configuration.class);
+ DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
+ Resources resources = mock(Resources.class);
+ when(mMockContext.getResources()).thenReturn(resources);
+ when(resources.getConfiguration()).thenReturn(configuration);
+ when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
+ TextView textView = mock(TextView.class);
+ // when(new TextView(mMockContext)).thenReturn(textView);
+ when(textView.getLineHeight()).thenReturn(16);
+ when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
+ mPeopleTileViewHelper = new PeopleTileViewHelper(mContext,
+ PERSON_TILE, 0, mOptions);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithLastInteractionTime() {
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction.
+ TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // No availability.
+ assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show name over predefined icon.
+ assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction.
+ lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // No availability.
+ assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
+ PeopleSpaceTile tileWithAvailabilityAndNewStory =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
+ new ConversationStatus.Builder(
+ PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+ ACTIVITY_GAME).build())).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction over status.
+ TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // Has availability.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show name rather than game type.
+ assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction.
+ lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // Has availability.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
+ PeopleSpaceTile tileWithStatusTemplate =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(
+ NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
+ PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+ ACTIVITY_BIRTHDAY).build())).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // Has status text from backup text.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show icon instead of name.
+ assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ View personIcon = largeResult.findViewById(R.id.person_icon);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ // Has notification content.
+ statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithStatusTemplate() {
+ PeopleSpaceTile tileWithStatusTemplate =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(GAME_STATUS,
+ NEW_STORY_WITH_AVAILABILITY)).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // Has status.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show icon instead of name.
+ assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ View personIcon = largeResult.findViewById(R.id.person_icon);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ // Has notification content.
+ statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithMissedCallNotification() {
+ PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
+ .setNotificationDataUri(null)
+ .setNotificationCategory(CATEGORY_MISSED_CALL)
+ .setNotificationContent(MISSED_CALL)
+ .build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithMissedCallNotification, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // Has missed call notification content.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), MISSED_CALL);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithMissedCallNotification, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show icon instead of name.
+ assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ tileWithMissedCallNotification, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.GONE, largeResult.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ View personIcon = largeResult.findViewById(R.id.person_icon);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ // Has notification content.
+ statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), MISSED_CALL);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithNotificationTemplate() {
+ PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+ .setNotificationDataUri(null)
+ .setStatuses(Arrays.asList(GAME_STATUS,
+ NEW_STORY_WITH_AVAILABILITY)).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // Has notification content.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show icon instead of name.
+ assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.GONE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ View personIcon = largeResult.findViewById(R.id.person_icon);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ // Has notification content.
+ statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageNoPunctuation() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageSingleExclamation() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test!");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageSingleQuestion() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("?test");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageSeparatedMarks() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test! right!");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageDoubleExclamation() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("!!test");
+
+ assertThat(backgroundText).isEqualTo("!");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageDoubleQuestion() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test??");
+
+ assertThat(backgroundText).isEqualTo("?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMixed() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test?!");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMixedInTheMiddle() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!? in the middle");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMixedDifferentOrder() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!? in the middle");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMultiple() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!?!!? in the middle");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageQuestionFirst() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test?? in the middle!!");
+
+ assertThat(backgroundText).isEqualTo("?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageExclamationFirst() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!! in the middle??");
+
+ assertThat(backgroundText).isEqualTo("!");
+ }
+
+ private int getSizeInDp(int dimenResourceId) {
+ return (int) (mContext.getResources().getDimension(dimenResourceId)
+ / mContext.getResources().getDisplayMetrics().density);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 4948c2b..3595095 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.Clock
@@ -78,8 +77,6 @@
@Mock
private lateinit var statusBarIconController: StatusBarIconController
@Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
private lateinit var demoModeController: DemoModeController
@Mock
private lateinit var userTracker: UserTracker
@@ -130,7 +127,6 @@
uiEventLogger,
qsTileHost,
statusBarIconController,
- commandQueue,
demoModeController,
userTracker,
quickQSPanelController,
@@ -233,4 +229,4 @@
`when`(privacyItemController.micCameraAvailable).thenReturn(micCamera)
`when`(privacyItemController.locationAvailable).thenReturn(location)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index e6287e7..aeb5b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -18,31 +18,18 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
import android.annotation.Nullable;
-import android.app.Notification;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.Drawable;
import android.test.suitebuilder.annotation.SmallTest;
-import android.widget.RemoteViews;
import androidx.palette.graphics.Palette;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.tests.R;
import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,17 +45,8 @@
*/
private static final int COLOR_TOLERANCE = 8;
- private MediaNotificationProcessor mProcessor;
- private Bitmap mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- private ImageGradientColorizer mColorizer;
@Nullable private Bitmap mArtwork;
- @Before
- public void setUp() {
- mColorizer = spy(new TestableColorizer(mBitmap));
- mProcessor = new MediaNotificationProcessor(getContext(), getContext(), mColorizer);
- }
-
@After
public void tearDown() {
if (mArtwork != null) {
@@ -78,53 +56,6 @@
}
@Test
- public void testColorizedWithLargeIcon() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setLargeIcon(mBitmap)
- .setContentText("Text");
- Notification notification = builder.build();
- mProcessor.processNotification(notification, builder);
- verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
- }
-
- @Test
- public void testNotColorizedWithoutLargeIcon() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
- Notification notification = builder.build();
- mProcessor.processNotification(notification, builder);
- verifyZeroInteractions(mColorizer);
- }
-
- @Test
- public void testRemoteViewsReset() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setStyle(new Notification.MediaStyle())
- .setLargeIcon(mBitmap)
- .setContentText("Text");
- Notification notification = builder.build();
- RemoteViews remoteViews = new RemoteViews(getContext().getPackageName(),
- R.layout.custom_view_dark);
- notification.contentView = remoteViews;
- notification.bigContentView = remoteViews;
- notification.headsUpContentView = remoteViews;
- mProcessor.processNotification(notification, builder);
- verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
- RemoteViews contentView = builder.createContentView();
- assertNotSame(contentView, remoteViews);
- contentView = builder.createBigContentView();
- assertNotSame(contentView, remoteViews);
- contentView = builder.createHeadsUpContentView();
- assertNotSame(contentView, remoteViews);
- }
-
- @Test
public void findBackgroundSwatch_white() {
// Given artwork that is completely white.
mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
@@ -153,17 +84,4 @@
assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
}
-
- public static class TestableColorizer extends ImageGradientColorizer {
- private final Bitmap mBitmap;
-
- private TestableColorizer(Bitmap bitmap) {
- mBitmap = bitmap;
- }
-
- @Override
- public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
- return mBitmap;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
deleted file mode 100644
index fbe4d73..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.notification.row.wrapper;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.Notification;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.SeekBar;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase {
-
- private ExpandableNotificationRow mRow;
- private Notification mNotif;
- private View mView;
- private NotificationMediaTemplateViewWrapper mWrapper;
-
- @Mock
- private MetricsLogger mMetricsLogger;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- allowTestableLooperAsMainThread();
-
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-
- // These tests are for regular media style notifications, not controls in quick settings
- Settings.System.putInt(mContext.getContentResolver(), "qs_media_player", 0);
- }
-
- private void makeTestNotification(long duration, boolean allowSeeking) throws Exception {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
-
- MediaMetadata metadata = new MediaMetadata.Builder()
- .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
- .build();
- MediaSession session = new MediaSession(mContext, "TEST_CHANNEL");
- session.setMetadata(metadata);
-
- PlaybackState playbackState = new PlaybackState.Builder()
- .setActions(allowSeeking ? PlaybackState.ACTION_SEEK_TO : 0)
- .build();
-
- session.setPlaybackState(playbackState);
-
- builder.setStyle(new Notification.MediaStyle()
- .setMediaSession(session.getSessionToken())
- );
-
- mNotif = builder.build();
- assertTrue(mNotif.hasMediaSession());
-
- NotificationTestHelper helper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = helper.createRow(mNotif);
-
- RemoteViews views = new RemoteViews(mContext.getPackageName(),
- com.android.internal.R.layout.notification_template_material_big_media);
- mView = views.apply(mContext, null);
- mWrapper = new NotificationMediaTemplateViewWrapper(mContext,
- mView, mRow);
- mWrapper.onContentUpdated(mRow);
- }
-
- @Test
- public void testLogging_NoSeekbar() throws Exception {
- // Media sessions with duration <= 0 should not include a seekbar
- makeTestNotification(0, false);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_CLOSE
- ));
-
- verify(mMetricsLogger, times(0)).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
- }
-
- @Test
- public void testLogging_HasSeekbarNoScrubber() throws Exception {
- // Media sessions that do not support seeking should have a seekbar, but no scrubber
- makeTestNotification(1000, false);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
-
- // Ensure the callback runs at least once
- mWrapper.mOnUpdateTimerTick.run();
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_DETAIL
- && logMaker.getSubtype() == 0
- ));
- }
-
- @Test
- public void testLogging_HasSeekbarAndScrubber() throws Exception {
- makeTestNotification(1000, true);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_DETAIL
- && logMaker.getSubtype() == 1
- ));
- }
-
- @Test
- public void testLogging_UpdateSeekbar() throws Exception {
- makeTestNotification(1000, true);
-
- SeekBar seekbar = mView.findViewById(
- com.android.internal.R.id.notification_media_progress_bar);
- assertTrue(seekbar != null);
-
- mWrapper.mSeekListener.onStopTrackingTouch(seekbar);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_UPDATE));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 7b7e2d3..f147f1ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -31,7 +31,6 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -60,14 +59,14 @@
@Test
public void testSetCalledOnAdd_IconManager() {
LinearLayout layout = new LinearLayout(mContext);
- TestIconManager manager = new TestIconManager(layout, new CommandQueue(mContext));
+ TestIconManager manager = new TestIconManager(layout);
testCallOnAdd_forManager(manager);
}
@Test
public void testSetCalledOnAdd_DarkIconManager() {
LinearLayout layout = new LinearLayout(mContext);
- TestDarkIconManager manager = new TestDarkIconManager(layout, new CommandQueue(mContext));
+ TestDarkIconManager manager = new TestDarkIconManager(layout);
testCallOnAdd_forManager(manager);
}
@@ -104,8 +103,8 @@
private static class TestDarkIconManager extends DarkIconManager
implements TestableIconManager {
- TestDarkIconManager(LinearLayout group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ TestDarkIconManager(LinearLayout group) {
+ super(group);
}
@Override
@@ -139,8 +138,8 @@
}
private static class TestIconManager extends IconManager implements TestableIconManager {
- TestIconManager(ViewGroup group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ TestIconManager(ViewGroup group) {
+ super(group);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 999d282..8f36415 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -35,7 +35,6 @@
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
-import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
@@ -54,7 +53,6 @@
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.testing.TestableLooper;
@@ -172,7 +170,7 @@
mMockNsm = mock(NetworkScoreManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mNetCapabilities = new NetworkCapabilities();
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
+ when(mMockTm.isDataCapable()).thenReturn(true);
when(mMockTm.createForSubscriptionId(anyInt())).thenReturn(mMockTm);
doAnswer(invocation -> {
int rssi = invocation.getArgument(0);
@@ -285,7 +283,7 @@
}
protected NetworkControllerImpl setUpNoMobileData() {
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ when(mMockTm.isDataCapable()).thenReturn(false);
NetworkControllerImpl networkControllerNoMobile =
new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm,
mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
@@ -308,27 +306,14 @@
TelephonyManager.NETWORK_TYPE_UMTS);
setConnectivityViaCallbackInNetworkController(
NetworkCapabilities.TRANSPORT_CELLULAR, true, true, null);
- setConnectivityViaBroadcast(
- NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
}
- public void setConnectivityViaBroadcastForVcn(
+ public void setConnectivityViaCallbackInNetworkControllerForVcn(
int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
mNetCapabilities.setTransportInfo(info);
setConnectivityCommon(networkType, validated, isConnected);
mDefaultCallbackInNetworkController.onCapabilitiesChanged(
mock(Network.class), new NetworkCapabilities(mNetCapabilities));
- Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
- mNetworkController.onReceive(mContext, i);
- }
-
- public void setConnectivityViaBroadcast(
- int networkType, boolean validated, boolean isConnected) {
- setConnectivityCommon(networkType, validated, isConnected);
- mDefaultCallbackInNetworkController.onCapabilitiesChanged(
- mock(Network.class), new NetworkCapabilities(mNetCapabilities));
- Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
- mNetworkController.onReceive(mContext, i);
}
public void setConnectivityViaCallbackInNetworkController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 37b6a5d..b108dd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -126,7 +126,8 @@
when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -140,7 +141,8 @@
when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -155,7 +157,8 @@
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -170,7 +173,8 @@
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -184,7 +188,8 @@
when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(false);
mUserCallback.onUserSetupChanged();
TestableLooper.get(this).processAllMessages();
@@ -206,7 +211,8 @@
mConfig.alwaysShowDataRatIcon = true;
mNetworkController.handleConfigurationChanged();
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, TelephonyIcons.ICON_G,
true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
index 93cf3e8..6aab9c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
@@ -37,7 +37,8 @@
}
protected void setEthernetState(boolean connected, boolean validated) {
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_ETHERNET, validated, connected);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_ETHERNET, validated, connected, null);
}
protected void verifyLastEthernetIcon(boolean visible, int icon) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index c0d9c3d..91e9f06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -23,8 +23,8 @@
import static org.mockito.Mockito.when;
import android.content.Intent;
-import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
+import android.net.wifi.WifiInfo;
import android.os.Handler;
import android.os.Looper;
import android.telephony.CellSignalStrength;
@@ -59,7 +59,7 @@
@Test
public void testNoIconWithoutMobile() {
// Turn off mobile network support.
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ when(mMockTm.isDataCapable()).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
@@ -145,7 +145,7 @@
@Test
public void testNoSimlessIconWithoutMobile() {
// Turn off mobile network support.
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ when(mMockTm.isDataCapable()).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
@@ -172,7 +172,8 @@
testStrength, DEFAULT_ICON);
// Verify low inet number indexing.
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, true, null);
verifyLastMobileDataIndicators(true,
testStrength, DEFAULT_ICON, false, false);
}
@@ -259,8 +260,10 @@
@Test
public void testNoBangWithWifi() {
setupDefaultSignal();
- setConnectivityViaBroadcast(mMobileSignalController.mTransportType, false, false);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ mMobileSignalController.mTransportType, false, false, null);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mock(WifiInfo.class));
verifyLastMobileDataIndicators(false, DEFAULT_LEVEL, 0);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 092c116..ab7cbf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -59,9 +59,11 @@
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
// Icon does not show if not validated
verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
}
@@ -80,12 +82,14 @@
setWifiState(true, testSsid);
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
setConnectivityViaDefaultCallbackInWifiTracker(
NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel],
testSsid);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
testSsid);
}
@@ -99,7 +103,8 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
setConnectivityViaDefaultCallbackInWifiTracker(
NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastQsWifiIcon(true, true,
@@ -126,14 +131,17 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setupDefaultSignal();
setGsmRoaming(true);
// Still be on wifi though.
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
verifyLastMobileDataIndicators(true, DEFAULT_LEVEL, 0, true);
}
@@ -145,7 +153,8 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setConnectivityViaCallbackInNetworkController(
@@ -161,7 +170,8 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setWifiState(false, testSsid);
@@ -234,11 +244,11 @@
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevelForVcn(testLevel);
- setConnectivityViaBroadcastForVcn(
+ setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
- setConnectivityViaBroadcastForVcn(
+ setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 6067b42..eb6fc2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -23,7 +23,6 @@
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SETTINGS;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SYSUI;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_THEME_PICKER;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SHAPE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.SETTINGS_PACKAGE;
@@ -116,8 +115,6 @@
ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, false),
- createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE,
- ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_FONT,
ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
@@ -128,8 +125,6 @@
ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, true),
- createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE,
- ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_FONT,
ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 8a0ac11..4e7e0a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.theme;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import static com.google.common.truth.Truth.assertThat;
@@ -147,8 +146,6 @@
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
.isEqualTo(new OverlayIdentifier("ffff0000"));
- assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_NEUTRAL_PALETTE))
- .isEqualTo(new OverlayIdentifier("ffff0000"));
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
.isEqualTo(new OverlayIdentifier("ff0000ff"));
diff --git a/proto/src/camera.proto b/proto/src/camera.proto
new file mode 100644
index 0000000..d07a525
--- /dev/null
+++ b/proto/src/camera.proto
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package android.stats.camera;
+
+option java_package = "android.stats.camera";
+option java_outer_classname = "CameraProtos";
+
+/**
+ * CameraStreamProto from atoms.proto, duplicated here so that it's accessible from the
+ * logging code. Must be kept in sync with the definition in atoms.proto.
+ */
+message CameraStreamProto {
+ // The stream width (in pixels)
+ optional int32 width = 1;
+ // The stream height (in pixels)
+ optional int32 height = 2;
+ // The format of the stream
+ optional int32 format = 3;
+ // The dataspace of the stream
+ optional int32 data_space = 4;
+ // The usage flag of the stream
+ optional int64 usage = 5;
+
+ // The number of requests for this stream
+ optional int64 request_count = 6;
+ // The number of buffer error for this stream
+ optional int64 error_count = 7;
+ // The capture latency of first request for this stream
+ optional int32 first_capture_latency_millis = 8;
+
+ // The maximum number of hal buffers
+ optional int32 max_hal_buffers = 9;
+ // The maximum number of app buffers
+ optional int32 max_app_buffers = 10;
+
+ // Type of stream histogram
+ // 1: Capture latency: bin size in milliseconds
+ enum HistogramType {
+ UNKNOWN = 0;
+ CAPTURE_LATENCY = 1;
+ }
+ optional HistogramType histogram_type = 11;
+ // The boundary values between histogram bins
+ // Expected number of fields: 9
+ repeated float histogram_bins = 12;
+ // The frame counts for each histogram bins
+ // Expected number of fields: 10
+ repeated int64 histogram_counts = 13;
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index e9a099a..2c50389 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -35,6 +35,7 @@
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.Slog;
@@ -179,7 +180,8 @@
Context ctx = getContext();
if (!(ctx.checkCallingPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED
|| mServiceNameResolver.isTemporary(userId)
- || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())
+ || Binder.getCallingUid() == Process.SYSTEM_UID)) {
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid()
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 735f420..cd332a6 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -40,6 +40,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.infra.AbstractRemoteService;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -55,6 +56,8 @@
private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX =
"predict_using_people_service_";
+ private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
+
@Nullable
@GuardedBy("mLock")
@@ -112,8 +115,16 @@
@GuardedBy("mLock")
public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
@NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
- final boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false);
+ if (context.getExtras() != null
+ && context.getExtras().getBoolean(REMOTE_APP_PREDICTOR_KEY, false)
+ && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED, false)
+ ) {
+ // connect with remote AppPredictionService instead for dark launch
+ usesPeopleService = false;
+ }
final boolean serviceExists = resolveService(sessionId, false,
usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 9ac93d9..b160d78 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1205,8 +1205,6 @@
}
public void schedule() {
- Slog.d(LOG_TAG,
- "TriggerDeviceDisappearedRunnable.schedule(address = " + mAddress + ")");
mMainHandler.removeCallbacks(this);
mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
}
@@ -1244,8 +1242,6 @@
}
private void onDeviceNearby(String address) {
- Slog.i(LOG_TAG, "onDeviceNearby(address = " + address + ")");
-
Date timestamp = new Date();
Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
@@ -1259,13 +1255,11 @@
boolean justAppeared = oldTimestamp == null
|| timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
if (justAppeared) {
+ Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Device " + address
- + " managed by " + association.getPackageName()
- + " is nearby on " + timestamp);
- }
+ Slog.i(LOG_TAG,
+ "Sending onDeviceAppeared to " + association.getPackageName() + ")");
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
}
@@ -1279,12 +1273,8 @@
boolean hasDeviceListeners = false;
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Device " + address
- + " managed by " + association.getPackageName()
- + " disappeared; last seen on " + mDevicesLastNearby.get(address));
- }
-
+ Slog.i(LOG_TAG,
+ "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceDisappeared(address));
hasDeviceListeners = true;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index ed2e625..5f7016e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -222,10 +222,12 @@
srcs: [
"java/com/android/server/ConnectivityService.java",
"java/com/android/server/ConnectivityServiceInitializer.java",
+ "java/com/android/server/NetIdManager.java",
"java/com/android/server/TestNetworkService.java",
"java/com/android/server/connectivity/AutodestructReference.java",
"java/com/android/server/connectivity/ConnectivityConstants.java",
"java/com/android/server/connectivity/DnsManager.java",
+ "java/com/android/server/connectivity/FullScore.java",
"java/com/android/server/connectivity/KeepaliveTracker.java",
"java/com/android/server/connectivity/LingerMonitor.java",
"java/com/android/server/connectivity/MockableSystemProperties.java",
@@ -234,7 +236,9 @@
"java/com/android/server/connectivity/NetworkDiagnostics.java",
"java/com/android/server/connectivity/NetworkNotificationManager.java",
"java/com/android/server/connectivity/NetworkRanker.java",
+ "java/com/android/server/connectivity/OsCompat.java",
"java/com/android/server/connectivity/PermissionMonitor.java",
+ "java/com/android/server/connectivity/ProfileNetworkPreferences.java",
"java/com/android/server/connectivity/ProxyTracker.java",
"java/com/android/server/connectivity/QosCallbackAgentConnection.java",
"java/com/android/server/connectivity/QosCallbackTracker.java",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4db4d1d..fbba189 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -87,7 +87,6 @@
import static java.util.Map.Entry;
import android.Manifest;
-import android.annotation.BoolRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -146,7 +145,6 @@
import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
-import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTestResultParcelable;
@@ -173,13 +171,14 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.net.networkstack.NetworkStackClientBase;
import android.net.resolv.aidl.DnsHealthEventParcel;
import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Build;
@@ -1122,10 +1121,10 @@
}
/**
- * Get a reference to the NetworkStackClient.
+ * Get a reference to the ModuleNetworkStackClient.
*/
- public NetworkStackClient getNetworkStack() {
- return NetworkStackClient.getInstance();
+ public NetworkStackClientBase getNetworkStack() {
+ return ModuleNetworkStackClient.getInstance(null);
}
/**
@@ -1184,7 +1183,8 @@
public ConnectivityService(Context context) {
this(context, getDnsResolver(context), new IpConnectivityLog(),
- NetdService.getInstance(), new Dependencies());
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+ new Dependencies());
}
@VisibleForTesting
@@ -1389,7 +1389,7 @@
mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
- private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, @BoolRes int id) {
+ private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) {
final boolean enable = mContext.getResources().getBoolean(id);
handleAlwaysOnNetworkRequest(networkRequest, enable);
}
@@ -2905,10 +2905,6 @@
}
pw.println();
- pw.println("NetworkStackClient logs:");
- pw.increaseIndent();
- NetworkStackClient.getInstance().dump(pw);
- pw.decreaseIndent();
pw.println();
pw.println("Permission Monitor:");
@@ -5090,7 +5086,7 @@
@Override
public void setRequireVpnForUids(boolean requireVpn, UidRange[] ranges) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
+ enforceNetworkStackOrSettingsPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_REQUIRE_VPN_FOR_UIDS,
encodeBool(requireVpn), 0 /* arg2 */, ranges));
}
@@ -5128,7 +5124,7 @@
@Override
public void setLegacyLockdownVpnEnabled(boolean enabled) {
- enforceSettingsPermission();
+ enforceNetworkStackOrSettingsPermission();
mHandler.post(() -> mLockdownEnabled = enabled);
}
@@ -6121,10 +6117,15 @@
private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
@NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid,
@NonNull final String requestorPackageName) {
+ // These capabilities are for a TRACK_DEFAULT callback, so:
+ // 1. Remove NET_CAPABILITY_VPN, because it's (currently!) the only difference between
+ // mDefaultRequest and a per-UID default request.
+ // TODO: stop depending on the fact that these two unrelated things happen to be the same
+ // 2. Always set the UIDs to mAsUid. restrictRequestUidsForCallerAndSetRequestorInfo will
+ // not do this in the case of a privileged application.
final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setSingleUid(requestorUid);
- netCap.setUids(new ArraySet<>());
restrictRequestUidsForCallerAndSetRequestorInfo(
netCap, requestorUid, requestorPackageName);
return netCap;
@@ -8410,7 +8411,7 @@
}
}
- private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+ private int getVpnType(@Nullable NetworkAgentInfo vpn) {
if (vpn == null) return VpnManager.TYPE_VPN_NONE;
final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f04af8b..0c3d884 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -448,7 +448,7 @@
final GZIPOutputStream gzipOutputStream =
new GZIPOutputStream(new FileOutputStream(fd));
FileUtils.copy(in, gzipOutputStream);
- gzipOutputStream.finish();
+ gzipOutputStream.close();
} else {
FileUtils.copy(in, new FileOutputStream(fd));
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a09dbc7..ceb12c8 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -21,6 +21,21 @@
}
],
"file_patterns": ["NotificationManagerService\\.java"]
+ },
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "include-filter": "android.content.cts.ClipboardManagerTest"
+ },
+ {
+ "include-filter": "android.content.cts.ClipDataTest"
+ },
+ {
+ "include-filter": "android.content.cts.ClipDescriptionTest"
+ }
+ ],
+ "file_patterns": ["ClipboardService\\.java"]
}
],
"presubmit-large": [
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index f566277..09873f4 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -35,7 +35,6 @@
import android.net.RouteInfo;
import android.net.TestNetworkInterface;
import android.net.TestNetworkSpecifier;
-import android.net.util.NetdService;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -86,7 +85,9 @@
mHandler = new Handler(mHandlerThread.getLooper());
mContext = Objects.requireNonNull(context, "missing Context");
- mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance");
+ mNetd = Objects.requireNonNull(
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+ "could not get netd instance");
mCm = mContext.getSystemService(ConnectivityManager.class);
mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
TEST_NETWORK_PROVIDER_NAME);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 1b352c7..d2fd8ff 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5663,8 +5663,8 @@
}
if (ret == REASON_DENIED) {
- if (mAm.checkPermission(SYSTEM_ALERT_WINDOW, callingPid,
- callingUid) == PERMISSION_GRANTED) {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c4548a3..492759f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -182,6 +182,7 @@
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
+import android.app.job.JobParameters;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManager;
@@ -3490,7 +3491,9 @@
// Clear its scheduled jobs
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
- js.cancelJobsForUid(appInfo.uid, "clear data");
+ // Clearing data is akin to uninstalling. The app is force stopped before we
+ // get to this point, so the reason won't be checked by the app.
+ js.cancelJobsForUid(appInfo.uid, JobParameters.STOP_REASON_USER, "clear data");
// Clear its pending alarms
AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index d8d6528..70a54cf 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -143,27 +143,45 @@
* Request that autofill data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
*/
public void requestAutofillData(List<IBinder> activityTokens, int callingUid,
String callingPackage) {
requestData(activityTokens, true /* requestAutofillData */,
true /* fetchData */, false /* fetchScreenshot */,
true /* allowFetchData */, false /* allowFetchScreenshot */,
- callingUid, callingPackage);
+ false /* ignoreTopActivityCheck */, callingUid, callingPackage);
}
/**
* Request that assist data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
*/
public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
int callingUid, String callingPackage) {
+ requestAssistData(activityTokens, fetchData, fetchScreenshot, allowFetchData,
+ allowFetchScreenshot, false /* ignoreTopActivityCheck */, callingUid,
+ callingPackage);
+ }
+
+ /**
+ * Request that assist data be loaded asynchronously. The resulting data will be provided
+ * through the {@link AssistDataRequesterCallbacks}.
+ *
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
+ */
+ public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+ final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
+ boolean ignoreTopActivityCheck, int callingUid, String callingPackage) {
requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot,
- allowFetchData, allowFetchScreenshot, callingUid, callingPackage);
+ allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck, callingUid,
+ callingPackage);
}
/**
@@ -183,10 +201,13 @@
* is allowed to fetch the assist data
* @param allowFetchScreenshot to be joined with other checks, determines whether or not the
* requester is allowed to fetch the assist screenshot
+ * @param ignoreTopActivityCheck overrides the check for whether the activity is in focus when
+ * making the request. Used when passing an activity from Recents.
*/
private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData,
final boolean fetchData, final boolean fetchScreenshot, boolean allowFetchData,
- boolean allowFetchScreenshot, int callingUid, String callingPackage) {
+ boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
+ String callingPackage) {
// TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
// then no assist data is requested for any of the other activities
@@ -230,7 +251,8 @@
receiverExtras, topActivity, 0 /* flags */)
: mActivityTaskManager.requestAssistContextExtras(
ASSIST_CONTEXT_FULL, this, receiverExtras, topActivity,
- /* focused= */ i == 0, /* newSessionId= */ i == 0);
+ /* checkActivityIsTop= */ (i == 0)
+ && !ignoreTopActivityCheck, /* newSessionId= */ i == 0);
if (result) {
mPendingDataCount++;
} else if (i == 0) {
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index 4c9ab63..d789bbb 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -241,7 +241,7 @@
/**
* For a consumer of type {@link EnergyConsumerType#OTHER}, updates
* {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
- * charge consumed (in microcouloumbs) between the previously stored values and the passed-in
+ * charge consumed (in microcoulombs) between the previously stored values and the passed-in
* values.
*
* @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
@@ -341,11 +341,11 @@
return numOrdinals;
}
- /** Calculate charge consumption (in microcouloumbs) from a given energy and voltage */
+ /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
private long calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV) {
// To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
- // since the last snapshot. Round up to the nearest whole long.
- return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV + 1) / 2) / avgVoltageMV;
+ // since the last snapshot. Round off to the nearest whole long.
+ return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
}
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index ad5a65c..c6824d1 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -293,6 +293,35 @@
}
/**
+ * Get the hibernating packages for the given user. This is equivalent to the list of
+ * packages for the user that return true for {@link #isHibernatingForUser}.
+ */
+ @NonNull List<String> getHibernatingPackagesForUser(int userId) {
+ ArrayList<String> hibernatingPackages = new ArrayList<>();
+ if (!checkHibernationEnabled("getHibernatingPackagesForUser")) {
+ return hibernatingPackages;
+ }
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_APP_HIBERNATION,
+ "Caller does not have MANAGE_APP_HIBERNATION permission.");
+ userId = handleIncomingUser(userId, "getHibernatingPackagesForUser");
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ Slog.w(TAG, "Attempt to get hibernating packages for a stopped or nonexistent user "
+ + userId);
+ return hibernatingPackages;
+ }
+ synchronized (mLock) {
+ Map<String, UserLevelState> userStates = mUserStates.get(userId);
+ for (UserLevelState state : userStates.values()) {
+ if (state.hibernated) {
+ hibernatingPackages.add(state.packageName);
+ }
+ }
+ return hibernatingPackages;
+ }
+ }
+
+ /**
* Put an app into hibernation for a given user, allowing user-level optimizations to occur.
*
* @param pkgState package hibernation state
@@ -610,6 +639,11 @@
}
@Override
+ public List<String> getHibernatingPackagesForUser(int userId) {
+ return mService.getHibernatingPackagesForUser(userId);
+ }
+
+ @Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err, @NonNull String[] args,
@Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 81ce2d5..3cfaaf7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -197,7 +197,7 @@
return mListener;
}
- public final int getTargetUserId() {
+ public int getTargetUserId() {
return mTargetUserId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 20c25c3..6c480f1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -55,7 +55,7 @@
private static final String BASE_TAG = "BiometricScheduler";
// Number of recent operations to keep in our logs for dumpsys
- private static final int LOG_NUM_RECENT_OPERATIONS = 50;
+ protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
/**
* Contains all the necessary information for a HAL operation.
@@ -196,10 +196,10 @@
}
}
- @NonNull private final String mBiometricTag;
+ @NonNull protected final String mBiometricTag;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
- @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
+ @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalCallback mInternalCallback;
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
@VisibleForTesting @Nullable Operation mCurrentOperation;
@@ -294,11 +294,11 @@
return mInternalCallback;
}
- private String getTag() {
+ protected String getTag() {
return BASE_TAG + "/" + mBiometricTag;
}
- private void startNextOperationIfIdle() {
+ protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
return;
@@ -310,6 +310,7 @@
mCurrentOperation = mPendingOperations.poll();
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
+ Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send
// ERROR_CANCELED. No need to start this client.
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
new file mode 100644
index 0000000..8b9be83
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -0,0 +1,50 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricsProto;
+
+public abstract class StartUserClient<T> extends HalClientMonitor<T> {
+
+ public interface UserStartedCallback {
+ void onUserStarted(int newUserId);
+ }
+
+ @NonNull @VisibleForTesting
+ protected final UserStartedCallback mUserStartedCallback;
+
+ public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull UserStartedCallback callback) {
+ super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mUserStartedCallback = callback;
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_START_USER;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
new file mode 100644
index 0000000..62cd673
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -0,0 +1,50 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricsProto;
+
+public abstract class StopUserClient<T> extends HalClientMonitor<T> {
+
+ public interface UserStoppedCallback {
+ void onUserStopped();
+ }
+
+ @NonNull @VisibleForTesting
+ protected final UserStoppedCallback mUserStoppedCallback;
+
+ public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mUserStoppedCallback = callback;
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_STOP_USER;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
new file mode 100644
index 0000000..c0ea2b3
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -0,0 +1,123 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+
+/**
+ * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
+ */
+public class UserAwareBiometricScheduler extends BiometricScheduler {
+
+ private static final String BASE_TAG = "UaBiometricScheduler";
+
+ /**
+ * Interface to retrieve the owner's notion of the current userId. Note that even though
+ * the scheduler can determine this based on its history of processed clients, we should still
+ * query the owner since it may be cleared due to things like HAL death, etc.
+ */
+ public interface CurrentUserRetriever {
+ int getCurrentUserId();
+ }
+
+ public interface UserSwitchCallback {
+ @NonNull StopUserClient<?> getStopUserClient(int userId);
+ @NonNull StartUserClient<?> getStartUserClient(int newUserId);
+ }
+
+ @NonNull private final CurrentUserRetriever mCurrentUserRetriever;
+ @NonNull private final UserSwitchCallback mUserSwitchCallback;
+ @NonNull @VisibleForTesting final ClientFinishedCallback mClientFinishedCallback;
+
+ @VisibleForTesting
+ class ClientFinishedCallback implements BaseClientMonitor.Callback {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+ mHandler.post(() -> {
+ Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+
+ startNextOperationIfIdle();
+ });
+ }
+ }
+
+ @VisibleForTesting
+ UserAwareBiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull IBiometricService biometricService,
+ @NonNull CurrentUserRetriever currentUserRetriever,
+ @NonNull UserSwitchCallback userSwitchCallback) {
+ super(tag, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS);
+
+ mCurrentUserRetriever = currentUserRetriever;
+ mUserSwitchCallback = userSwitchCallback;
+ mClientFinishedCallback = new ClientFinishedCallback();
+ }
+
+ public UserAwareBiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull CurrentUserRetriever currentUserRetriever,
+ @NonNull UserSwitchCallback userSwitchCallback) {
+ this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
+ userSwitchCallback);
+ }
+
+ @Override
+ protected String getTag() {
+ return BASE_TAG + "/" + mBiometricTag;
+ }
+
+ @Override
+ protected void startNextOperationIfIdle() {
+ if (mCurrentOperation != null) {
+ Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+ return;
+ }
+ if (mPendingOperations.isEmpty()) {
+ Slog.d(getTag(), "No operations, returning to idle");
+ return;
+ }
+
+ final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
+ final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
+
+ if (nextUserId == currentUserId) {
+ super.startNextOperationIfIdle();
+ } else if (currentUserId == UserHandle.USER_NULL) {
+ Slog.d(getTag(), "User switch required, current user null, next: " + nextUserId);
+ final BaseClientMonitor startClient =
+ mUserSwitchCallback.getStartUserClient(nextUserId);
+ startClient.start(mClientFinishedCallback);
+ } else {
+ final BaseClientMonitor stopClient = mUserSwitchCallback
+ .getStopUserClient(currentUserId);
+ Slog.d(getTag(), "User switch required, current: " + currentUserId
+ + ", next: " + nextUserId + ". " + stopClient);
+ stopClient.start(mClientFinishedCallback);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index ca29057..fdc3bb1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -254,7 +254,7 @@
mContext.getOpPackageName(), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
+ ", sensorId: " + sensorId
@@ -268,7 +268,7 @@
final InvalidationRequesterClient<Face> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
FaceUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@@ -318,7 +318,7 @@
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception", e);
}
@@ -468,7 +468,7 @@
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
}
@@ -523,7 +523,7 @@
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling remove", e);
}
@@ -599,7 +599,7 @@
FaceUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index c6f39aa..3eb4759 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -424,6 +424,12 @@
});
}
+ @Override
+ public void onSessionClosed() {
+ mHandler.post(() -> {
+ // TODO: implement this.
+ });
+ }
}
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 1b5def6..bcca69b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -291,7 +291,7 @@
mSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
+ ", sensorId: " + sensorId
@@ -305,7 +305,7 @@
final InvalidationRequesterClient<Fingerprint> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
FingerprintUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@@ -458,7 +458,7 @@
mSensors.get(sensorId).getLazySession(), token, callback, userId,
opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling finger detect", e);
}
@@ -492,7 +492,7 @@
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, allowBackgroundAuthentication);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
}
@@ -554,7 +554,7 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling remove", e);
}
@@ -583,7 +583,7 @@
mContext.getOpPackageName(), sensorId, enrolledList,
FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
}
@@ -627,7 +627,7 @@
new FingerprintInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 5631647..d843bc9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -403,6 +403,13 @@
invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
});
}
+
+ @Override
+ public void onSessionClosed() {
+ mHandler.post(() -> {
+ // TODO: implement this.
+ });
+ }
}
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 40d2073..00b39f1 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -38,7 +38,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserManager;
-import android.stats.camera.nano.CameraStreamProto;
+import android.stats.camera.nano.CameraProtos.CameraStreamProto;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 71565301..781bad7 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -106,16 +106,19 @@
return bits;
}
- private void openPipe() {
+ private boolean openPipe() {
try {
- mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
- mPipe.write(createOpenHandshake());
- } catch (IOException e) {
+ final RandomAccessFile pipe = new RandomAccessFile(PIPE_DEVICE, "rw");
try {
- if (mPipe != null) mPipe.close();
- } catch (IOException ee) {}
- mPipe = null;
+ pipe.write(createOpenHandshake());
+ mPipe = pipe;
+ return true;
+ } catch (IOException ignore) {
+ pipe.close();
+ }
+ } catch (IOException ignore) {
}
+ return false;
}
public HostClipboardMonitor(HostClipboardCallback cb) {
@@ -129,8 +132,7 @@
// There's no guarantee that QEMU pipes will be ready at the moment
// this method is invoked. We simply try to get the pipe open and
// retry on failure indefinitely.
- while (mPipe == null) {
- openPipe();
+ while ((mPipe == null) && !openPipe()) {
Thread.sleep(100);
}
int size = mPipe.readInt();
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 74e4ae7..acf39f0 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -26,6 +26,7 @@
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_NO_SUCH_SLOT;
import static android.net.SocketKeepalive.ERROR_STOP_REASON_UNINITIALIZED;
import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
@@ -528,6 +529,8 @@
}
} else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
throw new IllegalStateException("Unexpected stop reason: " + reason);
+ } else if (reason == ERROR_NO_SUCH_SLOT) {
+ throw new IllegalStateException("No such slot: " + reason);
} else {
notifyErrorCallback(ki.mCallback, reason);
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 0c0d459..b57ad5d8 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -84,7 +84,7 @@
// The context is for the current user (system server)
private final Context mContext;
- private final Resources mResources;
+ private final ConnectivityResources mResources;
private final TelephonyManager mTelephonyManager;
// The notification manager is created from a context for User.ALL, so notifications
// will be sent to all users.
@@ -99,7 +99,7 @@
(NotificationManager) c.createContextAsUser(UserHandle.ALL, 0 /* flags */)
.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationTypeMap = new SparseIntArray();
- mResources = new ConnectivityResources(mContext).get();
+ mResources = new ConnectivityResources(mContext);
}
@VisibleForTesting
@@ -118,11 +118,11 @@
}
private String getTransportName(final int transportType) {
- String[] networkTypes = mResources.getStringArray(R.array.network_switch_type_name);
+ String[] networkTypes = mResources.get().getStringArray(R.array.network_switch_type_name);
try {
return networkTypes[transportType];
} catch (IndexOutOfBoundsException e) {
- return mResources.getString(R.string.network_switch_type_name_unknown);
+ return mResources.get().getString(R.string.network_switch_type_name_unknown);
}
}
@@ -197,10 +197,11 @@
tag, nameOf(eventId), getTransportName(transportType), name, highPriority));
}
- final Resources r = mResources;
+ final Resources r = mResources.get();
final CharSequence title;
final CharSequence details;
- Icon icon = Icon.createWithResource(r, getIcon(transportType));
+ Icon icon = Icon.createWithResource(
+ mResources.getResourcesContext(), getIcon(transportType));
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.wifi_no_internet, name);
details = r.getString(R.string.wifi_no_internet_detailed);
@@ -355,7 +356,7 @@
public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
String fromTransport = getTransportName(approximateTransportType(fromNai));
String toTransport = getTransportName(approximateTransportType(toNai));
- String text = mResources.getString(
+ String text = mResources.get().getString(
R.string.network_switch_metered_toast, fromTransport, toTransport);
Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
}
diff --git a/services/core/java/com/android/server/connectivity/OsCompat.java b/services/core/java/com/android/server/connectivity/OsCompat.java
new file mode 100644
index 0000000..57e3dcd
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/OsCompat.java
@@ -0,0 +1,75 @@
+/*
+ * 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.connectivity;
+
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
+
+/**
+ * Compatibility utility for android.system.Os core platform APIs.
+ *
+ * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
+ * (only core_current). Most stable core platform APIs are included manually in the connectivity
+ * build rules, but because Os is also part of the base java SDK that is earlier on the
+ * classpath, the extra core platform APIs are not seen.
+ *
+ * TODO (b/157639992, b/183097033): remove as soon as core_current is part of system_server_current
+ * @hide
+ */
+public class OsCompat {
+ // This value should be correct on all architectures supported by Android, but hardcoding ioctl
+ // numbers should be avoided.
+ /**
+ * @see android.system.OsConstants#TIOCOUTQ
+ */
+ public static final int TIOCOUTQ = 0x5411;
+
+ /**
+ * @see android.system.Os#getsockoptInt(FileDescriptor, int, int)
+ */
+ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws
+ ErrnoException {
+ try {
+ return (int) Os.class.getMethod(
+ "getsockoptInt", FileDescriptor.class, int.class, int.class)
+ .invoke(null, fd, level, option);
+ } catch (ReflectiveOperationException e) {
+ if (e.getCause() instanceof ErrnoException) {
+ throw (ErrnoException) e.getCause();
+ }
+ throw new IllegalStateException("Error calling getsockoptInt", e);
+ }
+ }
+
+ /**
+ * @see android.system.Os#ioctlInt(FileDescriptor, int)
+ */
+ public static int ioctlInt(FileDescriptor fd, int cmd) throws
+ ErrnoException {
+ try {
+ return (int) Os.class.getMethod(
+ "ioctlInt", FileDescriptor.class, int.class).invoke(null, fd, cmd);
+ } catch (ReflectiveOperationException e) {
+ if (e.getCause() instanceof ErrnoException) {
+ throw (ErrnoException) e.getCause();
+ }
+ throw new IllegalStateException("Error calling ioctlInt", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index c480594..73f3475 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -27,7 +27,8 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IP_TOS;
import static android.system.OsConstants.IP_TTL;
-import static android.system.OsConstants.TIOCOUTQ;
+
+import static com.android.server.connectivity.OsCompat.TIOCOUTQ;
import android.annotation.NonNull;
import android.net.InvalidPacketException;
@@ -175,10 +176,10 @@
}
// Query write sequence number from SEND_QUEUE.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
- tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ tcpDetails.seq = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
// Query read sequence number from RECV_QUEUE.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
- tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ tcpDetails.ack = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
// Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
// Finally, check if socket is still idle. TODO : this check needs to move to
@@ -198,9 +199,9 @@
tcpDetails.rcvWndScale = trw.rcvWndScale;
if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
// Query TOS.
- tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+ tcpDetails.tos = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
// Query TTL.
- tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+ tcpDetails.ttl = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
}
} catch (ErrnoException e) {
Log.e(TAG, "Exception reading TCP state from socket", e);
@@ -305,7 +306,7 @@
private static boolean isReceiveQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- final int result = Os.ioctlInt(fd, SIOCINQ);
+ final int result = OsCompat.ioctlInt(fd, SIOCINQ);
if (result != 0) {
Log.e(TAG, "Read queue has data");
return false;
@@ -315,7 +316,7 @@
private static boolean isSendQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- final int result = Os.ioctlInt(fd, SIOCOUTQ);
+ final int result = OsCompat.ioctlInt(fd, SIOCOUTQ);
if (result != 0) {
Log.e(TAG, "Write queue has data");
return false;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 83866c6..70401b4 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,7 +17,6 @@
package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
-import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -1129,17 +1128,17 @@
}
/**
- * Return netId of current running VPN network.
+ * Return Network of current running VPN network.
*
- * @return a netId if there is a running VPN network or NETID_UNSET if there is no running VPN
+ * @return a Network if there is a running VPN network or null if there is no running VPN
* network or network is null.
*/
- public synchronized int getNetId() {
+ public synchronized Network getNetwork() {
final NetworkAgent agent = mNetworkAgent;
- if (null == agent) return NETID_UNSET;
+ if (null == agent) return null;
final Network network = agent.getNetwork();
- if (null == network) return NETID_UNSET;
- return network.getNetId();
+ if (null == network) return null;
+ return network;
}
private LinkProperties makeLinkProperties() {
@@ -1859,22 +1858,13 @@
/**
* Updates underlying network set.
*/
- public synchronized boolean setUnderlyingNetworks(Network[] networks) {
+ public synchronized boolean setUnderlyingNetworks(@Nullable Network[] networks) {
if (!isCallerEstablishedOwnerLocked()) {
return false;
}
- if (networks == null) {
- mConfig.underlyingNetworks = null;
- } else {
- mConfig.underlyingNetworks = new Network[networks.length];
- for (int i = 0; i < networks.length; ++i) {
- if (networks[i] == null) {
- mConfig.underlyingNetworks[i] = null;
- } else {
- mConfig.underlyingNetworks[i] = new Network(networks[i].getNetId());
- }
- }
- }
+ // Make defensive copy since the content of array might be altered by the caller.
+ mConfig.underlyingNetworks =
+ (networks != null) ? Arrays.copyOf(networks, networks.length) : null;
mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
? Arrays.asList(mConfig.underlyingNetworks) : null);
return true;
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index aaf9cbc..1f46061 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -119,7 +119,7 @@
public boolean onStopJob(JobParameters params) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
- + params.getStopReason());
+ + params.getLegacyStopReason());
}
final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
if (op == null) {
@@ -161,9 +161,9 @@
m.obj = op;
// Reschedule if this job was NOT explicitly canceled.
- m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
+ m.arg1 = params.getLegacyStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
// Apply backoff only if stop is called due to timeout.
- m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
+ m.arg2 = params.getLegacyStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
SyncManager.sendMessage(m);
return false;
@@ -204,7 +204,8 @@
return "job:null";
} else {
return "job:#" + params.getJobId() + ":"
- + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:"
+ + "sr=[" + params.getLegacyStopReason()
+ + "/" + params.getDebugStopReason() + "]:"
+ SyncOperation.maybeCreateFromJobExtras(params.getExtras());
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 0925027..0f13741 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -76,6 +76,8 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.VibrationEffect;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -1917,9 +1919,9 @@
}
private static class VibrationInfo {
- private long[] mPattern = new long[0];
- private int[] mAmplitudes = new int[0];
- private int mRepeat = -1;
+ private final long[] mPattern;
+ private final int[] mAmplitudes;
+ private final int mRepeat;
public long[] getPattern() {
return mPattern;
@@ -1934,40 +1936,55 @@
}
VibrationInfo(VibrationEffect effect) {
- // First replace prebaked effects with its fallback, if any available.
- if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect fallback = ((VibrationEffect.Prebaked) effect).getFallbackEffect();
- if (fallback != null) {
- effect = fallback;
+ long[] pattern = null;
+ int[] amplitudes = null;
+ int patternRepeatIndex = -1;
+ int amplitudeCount = -1;
+
+ if (effect instanceof VibrationEffect.Composed) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ int segmentCount = composed.getSegments().size();
+ pattern = new long[segmentCount];
+ amplitudes = new int[segmentCount];
+ patternRepeatIndex = composed.getRepeatIndex();
+ amplitudeCount = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if (composed.getRepeatIndex() == i) {
+ patternRepeatIndex = amplitudeCount;
+ }
+ if (!(segment instanceof StepSegment)) {
+ Slog.w(TAG, "Input devices don't support segment " + segment);
+ amplitudeCount = -1;
+ break;
+ }
+ float amplitude = ((StepSegment) segment).getAmplitude();
+ if (Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+ amplitudes[amplitudeCount] = DEFAULT_VIBRATION_MAGNITUDE;
+ } else {
+ amplitudes[amplitudeCount] =
+ (int) (amplitude * VibrationEffect.MAX_AMPLITUDE);
+ }
+ pattern[amplitudeCount++] = segment.getDuration();
}
}
- if (effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- mPattern = new long[] { 0, oneShot.getDuration() };
- int amplitude = oneShot.getAmplitude();
- // android framework uses DEFAULT_AMPLITUDE to signal that the vibration
- // should use some built-in default value, denoted here as
- // DEFAULT_VIBRATION_MAGNITUDE
- if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
- amplitude = DEFAULT_VIBRATION_MAGNITUDE;
- }
- mAmplitudes = new int[] { 0, amplitude };
+
+ if (amplitudeCount < 0) {
+ Slog.w(TAG, "Only oneshot and step waveforms are supported on input devices");
+ mPattern = new long[0];
+ mAmplitudes = new int[0];
mRepeat = -1;
- } else if (effect instanceof VibrationEffect.Waveform) {
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
- mPattern = waveform.getTimings();
- mAmplitudes = waveform.getAmplitudes();
- for (int i = 0; i < mAmplitudes.length; i++) {
- if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) {
- mAmplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE;
- }
- }
- mRepeat = waveform.getRepeatIndex();
- if (mRepeat >= mPattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
} else {
- Slog.w(TAG, "Pre-baked and composed effects aren't supported on input devices");
+ mRepeat = patternRepeatIndex;
+ mPattern = new long[amplitudeCount];
+ mAmplitudes = new int[amplitudeCount];
+ System.arraycopy(pattern, 0, mPattern, 0, amplitudeCount);
+ System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudeCount);
+ if (mRepeat >= mPattern.length) {
+ throw new ArrayIndexOutOfBoundsException("Repeat index " + mRepeat
+ + " must be within the bounds of the pattern.length "
+ + mPattern.length);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ffb532e..c9364c6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -17,6 +17,7 @@
import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
@@ -175,11 +176,9 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -199,6 +198,7 @@
import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerService;
+import com.android.server.utils.PriorityDump;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -256,7 +256,6 @@
static final int MSG_SET_ACTIVE = 3020;
static final int MSG_SET_INTERACTIVE = 3030;
static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
- static final int MSG_APPLY_IME_VISIBILITY = 3070;
static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
@@ -1567,7 +1566,7 @@
LocalServices.addService(InputMethodManagerInternal.class,
new LocalServiceImpl(mService));
publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,
- DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
+ DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@Override
@@ -3201,7 +3200,7 @@
boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
mShowRequested = true;
- if (mAccessibilityRequestingNoSoftKeyboard) {
+ if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
return false;
}
@@ -4102,7 +4101,6 @@
*/
@BinderThread
@Override
- @GuardedBy("mMethodMap")
public void startProtoDump(byte[] protoDump, int source, String where,
IVoidResultCallback resultCallback) {
CallbackUtils.onResult(resultCallback, () -> {
@@ -4199,7 +4197,6 @@
});
}
- @GuardedBy("mMethodMap")
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (mMethodMap) {
final long token = proto.start(fieldId);
@@ -4603,18 +4600,6 @@
}
return true;
}
- case MSG_APPLY_IME_VISIBILITY: {
- final boolean setVisible = msg.arg1 != 0;
- final ClientState clientState = (ClientState) msg.obj;
- try {
- clientState.client.applyImeVisibility(setVisible);
- } catch (RemoteException e) {
- Slog.w(TAG, "Got RemoteException sending "
- + "applyImeVisibility(" + setVisible + ") notification to pid="
- + clientState.pid + " uid=" + clientState.uid);
- }
- return true;
- }
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
@@ -5240,26 +5225,71 @@
}
}
+ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (asProto) {
+ dumpAsProtoNoCheck(fd);
+ } else {
+ dumpAsStringNoCheck(fd, pw, args, true /* isCritical */);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ dumpNormal(fd, pw, args, asProto);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ if (asProto) {
+ dumpAsProtoNoCheck(fd);
+ } else {
+ dumpAsStringNoCheck(fd, pw, args, false /* isCritical */);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @BinderThread
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ dumpNormal(fd, pw, args, asProto);
+ }
+
+ @BinderThread
+ private void dumpAsProtoNoCheck(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+ proto.flush();
+ }
+ };
+
+ @BinderThread
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- if (ArrayUtils.contains(args, PROTO_ARG)) {
- final ImeTracing imeTracing = ImeTracing.getInstance();
- if (imeTracing.isEnabled()) {
- imeTracing.stopTrace(null, false /* writeToFile */);
- BackgroundThread.getHandler().post(() -> {
- imeTracing.writeTracesToFiles();
- imeTracing.startTrace(null);
- });
- }
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ }
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
- dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
- proto.flush();
- return;
- }
-
+ @BinderThread
+ private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean isCritical) {
IInputMethod method;
ClientState client;
ClientState focusedWindowClient;
@@ -5323,6 +5353,11 @@
mSoftInputShowHideHistory.dump(pw, " ");
}
+ // Exit here for critical dump, as remaining sections require IPCs to other processes.
+ if (isCritical) {
+ return;
+ }
+
p.println(" ");
if (client != null) {
pw.flush();
@@ -5831,7 +5866,7 @@
}
/**
- * Handles {@code adb shell ime tracing start/stop}.
+ * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
* @param shellCommand {@link ShellCommand} object that is handling this command.
* @return Exit code of the command.
*/
@@ -5843,16 +5878,19 @@
switch (cmd) {
case "start":
ImeTracing.getInstance().getInstance().startTrace(pw);
- break;
+ break; // proceed to the next step to update the IME client processes.
case "stop":
ImeTracing.getInstance().stopTrace(pw);
- break;
+ break; // proceed to the next step to update the IME client processes.
+ case "save-for-bugreport":
+ ImeTracing.getInstance().saveForBugreport(pw);
+ return ShellCommandResult.SUCCESS; // no need to update the IME client processes.
default:
pw.println("Unknown command: " + cmd);
pw.println("Input method trace options:");
pw.println(" start: Start tracing");
pw.println(" stop: Stop tracing");
- return ShellCommandResult.FAILURE;
+ return ShellCommandResult.FAILURE; // no need to update the IME client processes.
}
boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
ArrayMap<IBinder, ClientState> clients;
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index c93c4b1..dc3596b 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
import java.util.Collections;
@@ -54,9 +55,11 @@
private final ServiceWatcher mServiceWatcher;
private GeocoderProxy(Context context) {
- mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION, null, null,
- com.android.internal.R.bool.config_enableGeocoderOverlay,
- com.android.internal.R.string.config_geocoderProviderPackageName);
+ mServiceWatcher = ServiceWatcher.create(context, "GeocoderProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+ com.android.internal.R.bool.config_enableGeocoderOverlay,
+ com.android.internal.R.string.config_geocoderProviderPackageName),
+ null);
}
private boolean register() {
@@ -72,7 +75,7 @@
*/
public void getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
@@ -97,7 +100,7 @@
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
index e1c8700..6ac6e77 100644
--- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
+++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
@@ -25,15 +25,15 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
/**
* Proxy class to bind GmsCore to the ActivityRecognitionHardware.
- *
- * @hide
*/
-public class HardwareActivityRecognitionProxy {
+public class HardwareActivityRecognitionProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "ARProxy";
private static final String SERVICE_ACTION =
@@ -66,12 +66,16 @@
mInstance = null;
}
- mServiceWatcher = new ServiceWatcher(context,
- SERVICE_ACTION,
- this::onBind,
- null,
- com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
- com.android.internal.R.string.config_activityRecognitionHardwarePackageName);
+ int useOverlayResId =
+ com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay;
+ int nonOverlayPackageResId =
+ com.android.internal.R.string.config_activityRecognitionHardwarePackageName;
+
+ mServiceWatcher = ServiceWatcher.create(context,
+ "HardwareActivityRecognitionProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION, useOverlayResId,
+ nonOverlayPackageResId),
+ this);
}
private boolean register() {
@@ -82,7 +86,8 @@
return resolves;
}
- private void onBind(IBinder binder, BoundService service) throws RemoteException {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
String descriptor = binder.getInterfaceDescriptor();
if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
@@ -99,4 +104,7 @@
Log.e(TAG, "Unknown descriptor: " + descriptor);
}
}
+
+ @Override
+ public void onUnbind() {}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2920ddb..864aa33 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -31,6 +31,7 @@
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -158,10 +159,9 @@
public Lifecycle(Context context) {
super(context);
- LocationEventLog eventLog = new LocationEventLog();
mUserInfoHelper = new LifecycleUserInfoHelper(context);
- mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog);
- mService = new LocationManagerService(context, mSystemInjector, eventLog);
+ mSystemInjector = new SystemInjector(context, mUserInfoHelper);
+ mService = new LocationManagerService(context, mSystemInjector);
}
@Override
@@ -233,7 +233,6 @@
private final Context mContext;
private final Injector mInjector;
- private final LocationEventLog mEventLog;
private final LocalService mLocalService;
private final GeofenceManager mGeofenceManager;
@@ -261,18 +260,20 @@
@GuardedBy("mLock")
private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener;
- LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) {
+ LocationManagerService(Context context, Injector injector) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mInjector = injector;
- mEventLog = eventLog;
mLocalService = new LocalService();
LocalServices.addService(LocationManagerInternal.class, mLocalService);
mGeofenceManager = new GeofenceManager(mContext, injector);
+ mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
+ this::onLocationModeChanged);
+
// set up passive provider first since it will be required for all other location providers,
// which are loaded later once the system is ready.
- mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog);
+ mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext));
// TODO: load the gps provider here as well, which will require refactoring
@@ -313,7 +314,7 @@
}
LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
- mEventLog, providerName, mPassiveManager);
+ providerName, mPassiveManager);
addLocationProviderManager(manager, null);
return manager;
}
@@ -335,7 +336,7 @@
Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
if (enableStationaryThrottling) {
realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
- mInjector, realProvider, mEventLog);
+ mInjector, realProvider);
}
}
manager.setRealProvider(realProvider);
@@ -355,9 +356,6 @@
}
void onSystemReady() {
- mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
- this::onLocationModeChanged);
-
if (Build.IS_DEBUGGABLE) {
// on debug builds, watch for location noteOps while location is off. there are some
// scenarios (emergency location) where this is expected, but generally this should
@@ -380,12 +378,13 @@
// provider has unfortunate hard dependencies on the network provider
ProxyLocationProvider networkProvider = ProxyLocationProvider.create(
mContext,
+ NETWORK_PROVIDER,
ACTION_NETWORK_PROVIDER,
com.android.internal.R.bool.config_enableNetworkLocationOverlay,
com.android.internal.R.string.config_networkLocationProviderPackageName);
if (networkProvider != null) {
LocationProviderManager networkManager = new LocationProviderManager(mContext,
- mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager);
+ mInjector, NETWORK_PROVIDER, mPassiveManager);
addLocationProviderManager(networkManager, networkProvider);
} else {
Log.w(TAG, "no network location provider found");
@@ -399,12 +398,13 @@
ProxyLocationProvider fusedProvider = ProxyLocationProvider.create(
mContext,
+ FUSED_PROVIDER,
ACTION_FUSED_PROVIDER,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName);
if (fusedProvider != null) {
LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
- mEventLog, FUSED_PROVIDER, mPassiveManager);
+ FUSED_PROVIDER, mPassiveManager);
addLocationProviderManager(fusedManager, fusedProvider);
} else {
Log.wtf(TAG, "no fused location provider found");
@@ -419,7 +419,7 @@
mGnssManagerService.onSystemReady();
LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
- mEventLog, GPS_PROVIDER, mPassiveManager);
+ GPS_PROVIDER, mPassiveManager);
addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
}
@@ -476,7 +476,7 @@
Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
}
- mEventLog.logLocationEnabled(userId, enabled);
+ EVENT_LOG.logLocationEnabled(userId, enabled);
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -1268,7 +1268,7 @@
ipw.println("Event Log:");
ipw.increaseIndent();
- mEventLog.iterate(manager.getName(), ipw::println);
+ EVENT_LOG.iterate(manager.getName(), ipw::println);
ipw.decreaseIndent();
return;
}
@@ -1313,7 +1313,7 @@
ipw.println("Historical Aggregate Location Provider Data:");
ipw.increaseIndent();
ArrayMap<String, ArrayMap<CallerIdentity, LocationEventLog.AggregateStats>> aggregateStats =
- mEventLog.copyAggregateStats();
+ EVENT_LOG.copyAggregateStats();
for (int i = 0; i < aggregateStats.size(); i++) {
ipw.print(aggregateStats.keyAt(i));
ipw.println(":");
@@ -1344,7 +1344,7 @@
ipw.println("Event Log:");
ipw.increaseIndent();
- mEventLog.iterate(ipw::println);
+ EVENT_LOG.iterate(ipw::println);
ipw.decreaseIndent();
}
@@ -1456,7 +1456,7 @@
@GuardedBy("this")
private boolean mSystemReady;
- SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) {
+ SystemInjector(Context context, UserInfoHelper userInfoHelper) {
mContext = context;
mUserInfoHelper = userInfoHelper;
@@ -1466,7 +1466,7 @@
mAppOpsHelper);
mSettingsHelper = new SystemSettingsHelper(context);
mAppForegroundHelper = new SystemAppForegroundHelper(context);
- mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
+ mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context);
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 29ce378..045e06d0 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -44,6 +44,8 @@
/** In memory event log for location events. */
public class LocationEventLog extends LocalEventLog {
+ public static final LocationEventLog EVENT_LOG = new LocationEventLog();
+
private static int getLogSize() {
if (Build.IS_DEBUGGABLE || D) {
return 500;
@@ -52,16 +54,17 @@
}
}
- private static final int EVENT_LOCATION_ENABLED = 1;
- private static final int EVENT_PROVIDER_ENABLED = 2;
- private static final int EVENT_PROVIDER_MOCKED = 3;
- private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
- private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
- private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
- private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
- private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
- private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
- private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
+ private static final int EVENT_USER_SWITCHED = 1;
+ private static final int EVENT_LOCATION_ENABLED = 2;
+ private static final int EVENT_PROVIDER_ENABLED = 3;
+ private static final int EVENT_PROVIDER_MOCKED = 4;
+ private static final int EVENT_PROVIDER_REGISTER_CLIENT = 5;
+ private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 6;
+ private static final int EVENT_PROVIDER_UPDATE_REQUEST = 7;
+ private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 8;
+ private static final int EVENT_PROVIDER_DELIVER_LOCATION = 9;
+ private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 10;
+ private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 11;
@GuardedBy("mAggregateStats")
private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
@@ -90,19 +93,24 @@
packageMap = new ArrayMap<>(2);
mAggregateStats.put(provider, packageMap);
}
- CallerIdentity stripped = identity.stripListenerId();
- AggregateStats stats = packageMap.get(stripped);
+ CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
+ AggregateStats stats = packageMap.get(aggregate);
if (stats == null) {
stats = new AggregateStats();
- packageMap.put(stripped, stats);
+ packageMap.put(aggregate, stats);
}
return stats;
}
}
+ /** Logs a user switched event. */
+ public void logUserSwitched(int userIdFrom, int userIdTo) {
+ addLogEvent(EVENT_USER_SWITCHED, userIdFrom, userIdTo);
+ }
+
/** Logs a location enabled/disabled event. */
public void logLocationEnabled(int userId, boolean enabled) {
- addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+ addLogEvent(EVENT_LOCATION_ENABLED, userId, enabled);
}
/** Logs a location provider enabled/disabled event. */
@@ -183,8 +191,10 @@
@Override
protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
switch (event) {
+ case EVENT_USER_SWITCHED:
+ return new UserSwitchedEvent(timeDelta, (Integer) args[0], (Integer) args[1]);
case EVENT_LOCATION_ENABLED:
- return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+ return new LocationEnabledEvent(timeDelta, (Integer) args[0], (Boolean) args[1]);
case EVENT_PROVIDER_ENABLED:
return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
(Boolean) args[2]);
@@ -397,6 +407,23 @@
}
}
+ private static final class UserSwitchedEvent extends LogEvent {
+
+ private final int mUserIdFrom;
+ private final int mUserIdTo;
+
+ UserSwitchedEvent(long timeDelta, int userIdFrom, int userIdTo) {
+ super(timeDelta);
+ mUserIdFrom = userIdFrom;
+ mUserIdTo = userIdTo;
+ }
+
+ @Override
+ public String getLogString() {
+ return "current user switched from u" + mUserIdFrom + " to u" + mUserIdTo;
+ }
+ }
+
private static final class LocationEnabledEvent extends LogEvent {
private final int mUserId;
@@ -410,7 +437,7 @@
@Override
public String getLogString() {
- return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+ return "location [u" + mUserId + "] " + (mEnabled ? "enabled" : "disabled");
}
}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
index c707149..90b446e 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
@@ -29,14 +29,17 @@
import android.os.UserHandle;
import android.util.Log;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
/**
* @hide
*/
-public final class GeofenceProxy {
+public final class GeofenceProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "GeofenceProxy";
private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider";
@@ -62,10 +65,12 @@
private GeofenceProxy(Context context, IGpsGeofenceHardware gpsGeofence) {
mGpsGeofenceHardware = Objects.requireNonNull(gpsGeofence);
- mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION,
- (binder, service) -> updateGeofenceHardware(binder), null,
- com.android.internal.R.bool.config_enableGeofenceOverlay,
- com.android.internal.R.string.config_geofenceProviderPackageName);
+ mServiceWatcher = ServiceWatcher.create(context,
+ "GeofenceProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+ com.android.internal.R.bool.config_enableGeofenceOverlay,
+ com.android.internal.R.string.config_geofenceProviderPackageName),
+ this);
mGeofenceHardware = null;
}
@@ -87,6 +92,14 @@
return resolves;
}
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
+ updateGeofenceHardware(binder);
+ }
+
+ @Override
+ public void onUnbind() {}
+
private class GeofenceProxyServiceConnection implements ServiceConnection {
GeofenceProxyServiceConnection() {}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index bcdfed4..21946ca 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -81,7 +81,7 @@
mGnssNative = gnssNative;
mGnssMetrics = new GnssMetrics(mContext, IBatteryStats.Stub.asInterface(
- ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+ ServiceManager.getService(BatteryStats.SERVICE_NAME)), mGnssNative);
mGnssLocationProvider = new GnssLocationProvider(mContext, injector, mGnssNative,
mGnssMetrics);
diff --git a/services/core/java/com/android/server/location/gnss/GnssMetrics.java b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
index c7d8144..dbc903d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMetrics.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
@@ -35,6 +35,7 @@
import com.android.internal.location.nano.GnssLogsProto.PowerMetrics;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.location.gnss.hal.GnssNative;
import java.util.ArrayList;
import java.util.Arrays;
@@ -52,11 +53,14 @@
/** Default time between location fixes (in millisecs) */
private static final int DEFAULT_TIME_BETWEEN_FIXES_MILLISECS = 1000;
+ private static final int CONVERT_MILLI_TO_MICRO = 1000;
+ private static final int VENDOR_SPECIFIC_POWER_MODES_SIZE = 10;
/** Frequency range of GPS L5, Galileo E5a, QZSS J5 frequency band */
private static final double L5_CARRIER_FREQ_RANGE_LOW_HZ = 1164 * 1e6;
private static final double L5_CARRIER_FREQ_RANGE_HIGH_HZ = 1189 * 1e6;
+
private long mLogStartInElapsedRealtimeMs;
GnssPowerMetrics mGnssPowerMetrics;
@@ -88,8 +92,10 @@
long mL5SvStatusReportsUsedInFix;
private final StatsManager mStatsManager;
+ private final GnssNative mGnssNative;
- public GnssMetrics(Context context, IBatteryStats stats) {
+ public GnssMetrics(Context context, IBatteryStats stats, GnssNative gnssNative) {
+ mGnssNative = gnssNative;
mGnssPowerMetrics = new GnssPowerMetrics(stats);
mLocationFailureStatistics = new Statistics();
mTimeToFirstFixSecStatistics = new Statistics();
@@ -189,8 +195,8 @@
}
/**
- * Logs sv status data
- */
+ * Logs sv status data
+ */
public void logSvStatus(GnssStatus status) {
boolean isL5;
// Calculate SvStatus Information
@@ -216,8 +222,8 @@
}
/**
- * Logs CN0 when at least 4 SVs are available L5 Only
- */
+ * Logs CN0 when at least 4 SVs are available L5 Only
+ */
private void logCn0L5(GnssStatus gnssStatus) {
if (gnssStatus.getSatelliteCount() == 0) {
return;
@@ -432,7 +438,8 @@
private double mSumSquare;
private long mLongSum;
- Statistics() {}
+ Statistics() {
+ }
/** Resets statistics */
public synchronized void reset() {
@@ -498,7 +505,7 @@
GnssPowerMetrics(IBatteryStats stats) {
mBatteryStats = stats;
// Used to initialize the variable to a very small value (unachievable in practice)
- // so that
+ // so that
// the first CNO report will trigger an update to BatteryStats
mLastAverageCn0 = -100.0;
mLastSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
@@ -585,6 +592,10 @@
FrameworkStatsLog.GNSS_STATS,
null, // use default PullAtomMetadata values
ConcurrentUtils.DIRECT_EXECUTOR, pullAtomCallback);
+ mStatsManager.setPullAtomCallback(
+ FrameworkStatsLog.GNSS_POWER_STATS,
+ null, // use default PullAtomMetadata values
+ ConcurrentUtils.DIRECT_EXECUTOR, pullAtomCallback);
}
/**
@@ -593,26 +604,68 @@
*/
private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
- StatsPullAtomCallbackImpl() {}
+ StatsPullAtomCallbackImpl() {
+ }
@Override
public int onPullAtom(int atomTag, List<StatsEvent> data) {
- if (atomTag != FrameworkStatsLog.GNSS_STATS) {
+ if (atomTag == FrameworkStatsLog.GNSS_STATS) {
+ data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+ mLocationFailureReportsStatistics.getCount(),
+ mLocationFailureReportsStatistics.getLongSum(),
+ mTimeToFirstFixMilliSReportsStatistics.getCount(),
+ mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
+ mPositionAccuracyMetersReportsStatistics.getCount(),
+ mPositionAccuracyMetersReportsStatistics.getLongSum(),
+ mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
+ mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
+ mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
+ mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
+ mSvStatusReportsUsedInFix, mL5SvStatusReports,
+ mL5SvStatusReportsUsedInFix));
+ } else if (atomTag == FrameworkStatsLog.GNSS_POWER_STATS) {
+ mGnssNative.requestPowerStats();
+ GnssPowerStats gnssPowerStats = mGnssNative.getPowerStats();
+ if (gnssPowerStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ double[] otherModesEnergyMilliJoule = new double[VENDOR_SPECIFIC_POWER_MODES_SIZE];
+ double[] tempGnssPowerStatsOtherModes =
+ gnssPowerStats.getOtherModesEnergyMilliJoule();
+ if (tempGnssPowerStatsOtherModes.length < VENDOR_SPECIFIC_POWER_MODES_SIZE) {
+ System.arraycopy(tempGnssPowerStatsOtherModes, 0,
+ otherModesEnergyMilliJoule, 0,
+ tempGnssPowerStatsOtherModes.length);
+ } else {
+ System.arraycopy(tempGnssPowerStatsOtherModes, 0,
+ otherModesEnergyMilliJoule, 0,
+ VENDOR_SPECIFIC_POWER_MODES_SIZE);
+ }
+ data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+ (long) (gnssPowerStats.getElapsedRealtimeUncertaintyNanos()),
+ (long) (gnssPowerStats.getTotalEnergyMilliJoule() * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getSinglebandTrackingModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getMultibandTrackingModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getSinglebandAcquisitionModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getMultibandAcquisitionModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[0] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[1] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[2] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[3] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[4] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[5] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[6] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[7] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[8] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[9] * CONVERT_MILLI_TO_MICRO)));
+ } else {
throw new UnsupportedOperationException("Unknown tagId = " + atomTag);
}
- data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
- mLocationFailureReportsStatistics.getCount(),
- mLocationFailureReportsStatistics.getLongSum(),
- mTimeToFirstFixMilliSReportsStatistics.getCount(),
- mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
- mPositionAccuracyMetersReportsStatistics.getCount(),
- mPositionAccuracyMetersReportsStatistics.getLongSum(),
- mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
- mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
- mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
- mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
- mSvStatusReportsUsedInFix, mL5SvStatusReports, mL5SvStatusReportsUsedInFix));
return StatsManager.PULL_SUCCESS;
}
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
index cc00d56..53407d9 100644
--- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
@@ -20,12 +20,11 @@
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import android.os.PowerManager.LocationPowerSaveMode;
import android.util.Log;
-import com.android.server.location.eventlog.LocationEventLog;
-
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -43,11 +42,9 @@
void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode);
}
- private final LocationEventLog mLocationEventLog;
private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners;
- public LocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
- mLocationEventLog = locationEventLog;
+ public LocationPowerSaveModeHelper() {
mListeners = new CopyOnWriteArrayList<>();
}
@@ -72,7 +69,7 @@
Log.d(TAG, "location power save mode is now " + locationPowerSaveModeToString(
locationPowerSaveMode));
}
- mLocationEventLog.logLocationPowerSaveMode(locationPowerSaveMode);
+ EVENT_LOG.logLocationPowerSaveMode(locationPowerSaveMode);
for (LocationPowerSaveModeChangedListener listener : mListeners) {
listener.onLocationPowerSaveModeChanged(locationPowerSaveMode);
diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
index c47a64d..a675f54 100644
--- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
@@ -25,7 +25,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import java.util.Objects;
import java.util.function.Consumer;
@@ -42,8 +41,7 @@
@LocationPowerSaveMode
private volatile int mLocationPowerSaveMode;
- public SystemLocationPowerSaveModeHelper(Context context, LocationEventLog locationEventLog) {
- super(locationEventLog);
+ public SystemLocationPowerSaveModeHelper(Context context) {
mContext = context;
}
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3f7345e..632ed6e 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -35,6 +35,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -348,31 +349,73 @@
*/
@Override
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
- int userId = ActivityManager.getCurrentUser();
+ int[] userIds;
+ try {
+ userIds = ActivityManager.getService().getRunningUserIds();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
- ipw.print("Location Enabled: ");
- ipw.println(isLocationEnabled(userId));
-
- List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(userId);
- if (!locationPackageBlacklist.isEmpty()) {
- ipw.println("Location Deny Packages:");
- ipw.increaseIndent();
- for (String packageName : locationPackageBlacklist) {
- ipw.println(packageName);
+ ipw.print("Location Setting: ");
+ ipw.increaseIndent();
+ if (userIds.length > 1) {
+ ipw.println();
+ for (int userId : userIds) {
+ ipw.print("[u");
+ ipw.print(userId);
+ ipw.print("] ");
+ ipw.println(isLocationEnabled(userId));
}
- ipw.decreaseIndent();
+ } else {
+ ipw.println(isLocationEnabled(userIds[0]));
+ }
+ ipw.decreaseIndent();
- List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
- userId);
- if (!locationPackageWhitelist.isEmpty()) {
- ipw.println("Location Allow Packages:");
+ ipw.println("Location Allow/Deny Packages:");
+ ipw.increaseIndent();
+ if (userIds.length > 1) {
+ for (int userId : userIds) {
+ List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+ userId);
+ if (locationPackageBlacklist.isEmpty()) {
+ continue;
+ }
+
+ ipw.print("user ");
+ ipw.print(userId);
+ ipw.println(":");
ipw.increaseIndent();
- for (String packageName : locationPackageWhitelist) {
+
+ for (String packageName : locationPackageBlacklist) {
+ ipw.print("[deny] ");
ipw.println(packageName);
}
+
+ List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+ userId);
+ for (String packageName : locationPackageWhitelist) {
+ ipw.print("[allow] ");
+ ipw.println(packageName);
+ }
+
ipw.decreaseIndent();
}
+ } else {
+ List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+ userIds[0]);
+ for (String packageName : locationPackageBlacklist) {
+ ipw.print("[deny] ");
+ ipw.println(packageName);
+ }
+
+ List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+ userIds[0]);
+ for (String packageName : locationPackageWhitelist) {
+ ipw.print("[allow] ");
+ ipw.println(packageName);
+ }
}
+ ipw.decreaseIndent();
Set<String> backgroundThrottlePackageWhitelist =
mBackgroundThrottlePackageWhitelist.getValue();
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index d4a8fbd..ed1e654 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -155,6 +155,11 @@
*/
@Override
public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ int[] runningUserIds = getRunningUserIds();
+ if (runningUserIds.length > 1) {
+ pw.println("running users: u" + Arrays.toString(runningUserIds));
+ }
+
ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
if (activityManagerInternal == null) {
return;
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index 0fcc1ec..c835370 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -18,6 +18,7 @@
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
@@ -105,6 +106,7 @@
Log.d(TAG, "current user changed from u" + Arrays.toString(fromUserIds) + " to u"
+ Arrays.toString(toUserIds));
}
+ EVENT_LOG.logUserSwitched(fromUserId, toUserId);
for (UserListener listener : mListeners) {
for (int userId : fromUserIds) {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index dc8b1d0..102263b 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -37,6 +37,7 @@
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -94,7 +95,6 @@
import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.fudger.LocationFudger;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
@@ -333,7 +333,7 @@
+ getRequest());
}
- mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+ EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -345,7 +345,7 @@
onProviderListenerRegister();
if (mForeground) {
- mEventLog.logProviderClientForeground(mName, getIdentity());
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
}
}
@@ -358,7 +358,7 @@
onProviderListenerUnregister();
- mEventLog.logProviderClientUnregistered(mName, getIdentity());
+ EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
@@ -383,7 +383,7 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
- mEventLog.logProviderClientActive(mName, getIdentity());
+ EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
@@ -406,7 +406,7 @@
onProviderListenerInactive();
- mEventLog.logProviderClientInactive(mName, getIdentity());
+ EVENT_LOG.logProviderClientInactive(mName, getIdentity());
}
/**
@@ -543,9 +543,9 @@
mForeground = foreground;
if (mForeground) {
- mEventLog.logProviderClientForeground(mName, getIdentity());
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
} else {
- mEventLog.logProviderClientBackground(mName, getIdentity());
+ EVENT_LOG.logProviderClientBackground(mName, getIdentity());
}
// note that onProviderLocationRequestChanged() is always called
@@ -654,7 +654,7 @@
protected abstract class LocationRegistration extends Registration implements
OnAlarmListener, ProviderEnabledListener {
- private final PowerManager.WakeLock mWakeLock;
+ final PowerManager.WakeLock mWakeLock;
private volatile ProviderTransport mProviderTransport;
private int mNumLocationsDelivered = 0;
@@ -879,7 +879,7 @@
listener.deliverOnLocationChanged(deliverLocationResult,
mUseWakeLock ? mWakeLock::release : null);
- mEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
+ EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
getIdentity());
}
@@ -1178,7 +1178,7 @@
// we currently don't hold a wakelock for getCurrentLocation deliveries
listener.deliverOnLocationChanged(deliverLocationResult, null);
- mEventLog.logProviderDeliveredLocations(mName,
+ EVENT_LOG.logProviderDeliveredLocations(mName,
locationResult != null ? locationResult.size() : 0, getIdentity());
}
@@ -1247,7 +1247,6 @@
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
- protected final LocationEventLog mEventLog;
protected final LocationManagerInternal mLocationManagerInternal;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserHelper;
@@ -1300,7 +1299,7 @@
@GuardedBy("mLock")
private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener;
- public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog,
+ public LocationProviderManager(Context context, Injector injector,
String name, @Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
mName = Objects.requireNonNull(name);
@@ -1312,7 +1311,6 @@
mEnabledListeners = new ArrayList<>();
mProviderRequestListeners = new CopyOnWriteArrayList<>();
- mEventLog = eventLog;
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mSettingsHelper = injector.getSettingsHelper();
@@ -1477,7 +1475,7 @@
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
- mEventLog.logProviderMocked(mName, provider != null);
+ EVENT_LOG.logProviderMocked(mName, provider != null);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1966,8 +1964,8 @@
}
@GuardedBy("mLock")
- private void setProviderRequest(ProviderRequest request) {
- mEventLog.logProviderUpdateRequest(mName, request);
+ void setProviderRequest(ProviderRequest request) {
+ EVENT_LOG.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
FgThread.getHandler().post(() -> {
@@ -2324,7 +2322,7 @@
}
// don't log location received for passive provider because it's spammy
- mEventLog.logProviderReceivedLocations(mName, filtered.size());
+ EVENT_LOG.logProviderReceivedLocations(mName, filtered.size());
} else {
// passive provider should get already filtered results as input
filtered = locationResult;
@@ -2424,7 +2422,7 @@
if (D) {
Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
}
- mEventLog.logProviderEnabled(mName, userId, enabled);
+ EVENT_LOG.logProviderEnabled(mName, userId, enabled);
}
// clear last locations if we become disabled
@@ -2464,7 +2462,7 @@
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- private @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
+ @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
@PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2477,7 +2475,7 @@
}
}
- private @Nullable LocationResult getPermittedLocationResult(
+ @Nullable LocationResult getPermittedLocationResult(
@Nullable LocationResult fineLocationResult, @PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2538,6 +2536,8 @@
private @Nullable Location mFineBypassLocation;
private @Nullable Location mCoarseBypassLocation;
+ LastLocation() {}
+
public void clearMock() {
if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
mFineLocation = null;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index 027f4e9..b35af4f 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -24,7 +24,6 @@
import android.os.Binder;
import com.android.internal.util.Preconditions;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.Injector;
import java.util.Collection;
@@ -34,9 +33,8 @@
*/
public class PassiveLocationProviderManager extends LocationProviderManager {
- public PassiveLocationProviderManager(Context context, Injector injector,
- LocationEventLog eventLog) {
- super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null);
+ public PassiveLocationProviderManager(Context context, Injector injector) {
+ super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 6f4aa64..ab7e526 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import android.annotation.Nullable;
import android.location.Location;
@@ -33,7 +34,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.DeviceIdleHelper;
import com.android.server.location.injector.DeviceStationaryHelper;
import com.android.server.location.injector.Injector;
@@ -54,12 +54,11 @@
private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
- private final Object mLock = new Object();
+ final Object mLock = new Object();
private final String mName;
private final DeviceIdleHelper mDeviceIdleHelper;
private final DeviceStationaryHelper mDeviceStationaryHelper;
- private final LocationEventLog mEventLog;
@GuardedBy("mLock")
private boolean mDeviceIdle = false;
@@ -72,21 +71,19 @@
@GuardedBy("mLock")
private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
@GuardedBy("mLock")
- private long mThrottlingIntervalMs = INTERVAL_DISABLED;
+ long mThrottlingIntervalMs = INTERVAL_DISABLED;
@GuardedBy("mLock")
- private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
-
+ @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
@GuardedBy("mLock")
- private @Nullable Location mLastLocation;
+ @Nullable Location mLastLocation;
public StationaryThrottlingLocationProvider(String name, Injector injector,
- AbstractLocationProvider delegate, LocationEventLog eventLog) {
+ AbstractLocationProvider delegate) {
super(DIRECT_EXECUTOR, delegate);
mName = name;
mDeviceIdleHelper = injector.getDeviceIdleHelper();
mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
- mEventLog = eventLog;
// must be last statement in the constructor because reference is escaping
initializeDelegate();
@@ -209,7 +206,7 @@
if (D) {
Log.d(TAG, mName + " provider stationary throttled");
}
- mEventLog.logProviderStationaryThrottled(mName, true);
+ EVENT_LOG.logProviderStationaryThrottled(mName, true);
}
if (mDeliverLastLocationCallback != null) {
@@ -227,7 +224,7 @@
}
} else {
if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
- mEventLog.logProviderStationaryThrottled(mName, false);
+ EVENT_LOG.logProviderStationaryThrottled(mName, false);
if (D) {
Log.d(TAG, mName + " provider stationary unthrottled");
}
@@ -257,6 +254,9 @@
}
private class DeliverLastLocationRunnable implements Runnable {
+
+ DeliverLastLocationRunnable() {}
+
@Override
public void run() {
Location location;
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index c86e49b..5df7870 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -19,7 +19,6 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.Context;
import android.location.Location;
import android.location.LocationResult;
@@ -28,42 +27,46 @@
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
import com.android.server.location.provider.AbstractLocationProvider;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
/**
* Proxy for ILocationProvider implementations.
*/
-public class ProxyLocationProvider extends AbstractLocationProvider {
+public class ProxyLocationProvider extends AbstractLocationProvider implements
+ ServiceListener<BoundServiceInfo> {
- private static final String KEY_EXTRA_ATTRIBUTION_TAGS = "android:location_allow_listed_tags";
- private static final String EXTRA_ATTRIBUTION_TAGS_SEPARATOR = ";";
+ private static final String EXTRA_LOCATION_TAGS = "android:location_allow_listed_tags";
+ private static final String LOCATION_TAGS_SEPARATOR = ";";
+
+ private static final long RESET_DELAY_MS = 1000;
/**
* Creates and registers this proxy. If no suitable service is available for the proxy, returns
* null.
*/
@Nullable
- public static ProxyLocationProvider create(Context context, String action,
+ public static ProxyLocationProvider create(Context context, String provider, String action,
int enableOverlayResId, int nonOverlayPackageResId) {
- ProxyLocationProvider proxy = new ProxyLocationProvider(context, action, enableOverlayResId,
- nonOverlayPackageResId);
+ ProxyLocationProvider proxy = new ProxyLocationProvider(context, provider, action,
+ enableOverlayResId, nonOverlayPackageResId);
if (proxy.checkServiceResolves()) {
return proxy;
} else {
@@ -80,21 +83,24 @@
final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0);
@GuardedBy("mLock")
- Proxy mProxy;
+ @Nullable Runnable mResetter;
@GuardedBy("mLock")
- @Nullable ComponentName mService;
+ @Nullable Proxy mProxy;
+ @GuardedBy("mLock")
+ @Nullable BoundServiceInfo mBoundServiceInfo;
private volatile ProviderRequest mRequest;
- private ProxyLocationProvider(Context context, String action, int enableOverlayResId,
- int nonOverlayPackageResId) {
+ private ProxyLocationProvider(Context context, String provider, String action,
+ int enableOverlayResId, int nonOverlayPackageResId) {
// safe to use direct executor since our locks are not acquired in a code path invoked by
// our owning provider
super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
mContext = context;
- mServiceWatcher = new ServiceWatcher(context, action, this::onBind,
- this::onUnbind, enableOverlayResId, nonOverlayPackageResId);
+ mServiceWatcher = ServiceWatcher.create(context, provider,
+ new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+ nonOverlayPackageResId), this);
mProxy = null;
mRequest = ProviderRequest.EMPTY_REQUEST;
@@ -104,21 +110,13 @@
return mServiceWatcher.checkServiceResolves();
}
- private void onBind(IBinder binder, BoundService boundService) throws RemoteException {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
synchronized (mLock) {
mProxy = new Proxy();
- mService = boundService.component;
-
- // update extra attribution tag info from manifest
- if (boundService.metadata != null) {
- String tagsList = boundService.metadata.getString(KEY_EXTRA_ATTRIBUTION_TAGS);
- if (tagsList != null) {
- setExtraAttributionTags(
- new ArraySet<>(tagsList.split(EXTRA_ATTRIBUTION_TAGS_SEPARATOR)));
- }
- }
+ mBoundServiceInfo = boundServiceInfo;
provider.setLocationProviderManager(mProxy);
@@ -129,12 +127,30 @@
}
}
- private void onUnbind() {
+ @Override
+ public void onUnbind() {
Runnable[] flushListeners;
synchronized (mLock) {
mProxy = null;
- mService = null;
- setState(prevState -> State.EMPTY_STATE);
+ mBoundServiceInfo = null;
+
+ // we need to clear the state - but most disconnections are very temporary. we give a
+ // grace period where we don't clear the state immediately so that transient
+ // interruptions are not necessarily visible to downstream clients
+ if (mResetter == null) {
+ mResetter = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mResetter == this) {
+ setState(prevState -> State.EMPTY_STATE);
+ }
+ }
+ }
+ };
+ FgThread.getHandler().postDelayed(mResetter, RESET_DELAY_MS);
+ }
+
flushListeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
@@ -166,7 +182,7 @@
@Override
protected void onFlush(Runnable callback) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
@@ -206,20 +222,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mServiceWatcher.dump(fd, pw, args);
- }
-
- private static String guessPackageName(Context context, int uid, String packageName) {
- String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
- if (packageNames == null || packageNames.length == 0) {
- // illegal state exception will propagate back through binders
- throw new IllegalStateException(
- "location provider from uid " + uid + " has no package information");
- } else if (ArrayUtils.contains(packageNames, packageName)) {
- return packageName;
- } else {
- return packageNames[0];
- }
+ mServiceWatcher.dump(pw);
}
private class Proxy extends ILocationProviderManager.Stub {
@@ -229,26 +232,37 @@
// executed on binder thread
@Override
public void onInitialize(boolean allowed, ProviderProperties properties,
- @Nullable String packageName, @Nullable String attributionTag) {
+ @Nullable String attributionTag) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
- CallerIdentity identity;
- if (packageName == null) {
- packageName = guessPackageName(mContext, Binder.getCallingUid(),
- Objects.requireNonNull(mService).getPackageName());
- // unsafe is ok since the package is coming direct from the package manager here
- identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
- } else {
- identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+ if (mResetter != null) {
+ FgThread.getHandler().removeCallbacks(mResetter);
+ mResetter = null;
}
- setState(prevState -> prevState
+ // set extra attribution tags from manifest if necessary
+ String[] attributionTags = new String[0];
+ if (mBoundServiceInfo.getMetadata() != null) {
+ String tagsStr = mBoundServiceInfo.getMetadata().getString(EXTRA_LOCATION_TAGS);
+ if (!TextUtils.isEmpty(tagsStr)) {
+ attributionTags = tagsStr.split(LOCATION_TAGS_SEPARATOR);
+ }
+ }
+ ArraySet<String> extraAttributionTags = new ArraySet<>(attributionTags);
+
+ // unsafe is ok since we trust the package name already
+ CallerIdentity identity = CallerIdentity.fromBinderUnsafe(
+ mBoundServiceInfo.getComponentName().getPackageName(),
+ attributionTag);
+
+ setState(prevState -> State.EMPTY_STATE
.withAllowed(allowed)
.withProperties(properties)
- .withIdentity(identity));
+ .withIdentity(identity)
+ .withExtraAttributionTags(extraAttributionTags));
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 46ece74..db2e908 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -563,8 +563,8 @@
final PowerExemptionManager powerExemptionManager = userContext.getSystemService(
PowerExemptionManager.class);
powerExemptionManager.addToTemporaryAllowList(targetPackage,
- FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS,
- PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason);
+ PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason,
+ FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS);
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index cc3a002..df1eb6d 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -22,7 +22,6 @@
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
import android.net.ProxyInfo;
-import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
import android.util.ArrayMap;
@@ -42,6 +41,8 @@
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
public class IpConfigStore {
private static final String TAG = "IpConfigStore";
@@ -83,25 +84,25 @@
boolean written = false;
try {
- switch (config.ipAssignment) {
+ switch (config.getIpAssignment()) {
case STATIC:
out.writeUTF(IP_ASSIGNMENT_KEY);
- out.writeUTF(config.ipAssignment.toString());
- StaticIpConfiguration staticIpConfiguration = config.staticIpConfiguration;
+ out.writeUTF(config.getIpAssignment().toString());
+ StaticIpConfiguration staticIpConfiguration = config.getStaticIpConfiguration();
if (staticIpConfiguration != null) {
- if (staticIpConfiguration.ipAddress != null) {
- LinkAddress ipAddress = staticIpConfiguration.ipAddress;
+ if (staticIpConfiguration.getIpAddress() != null) {
+ LinkAddress ipAddress = staticIpConfiguration.getIpAddress();
out.writeUTF(LINK_ADDRESS_KEY);
out.writeUTF(ipAddress.getAddress().getHostAddress());
out.writeInt(ipAddress.getPrefixLength());
}
- if (staticIpConfiguration.gateway != null) {
+ if (staticIpConfiguration.getGateway() != null) {
out.writeUTF(GATEWAY_KEY);
out.writeInt(0); // Default route.
out.writeInt(1); // Have a gateway.
- out.writeUTF(staticIpConfiguration.gateway.getHostAddress());
+ out.writeUTF(staticIpConfiguration.getGateway().getHostAddress());
}
- for (InetAddress inetAddr : staticIpConfiguration.dnsServers) {
+ for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
out.writeUTF(DNS_KEY);
out.writeUTF(inetAddr.getHostAddress());
}
@@ -110,7 +111,7 @@
break;
case DHCP:
out.writeUTF(IP_ASSIGNMENT_KEY);
- out.writeUTF(config.ipAssignment.toString());
+ out.writeUTF(config.getIpAssignment().toString());
written = true;
break;
case UNASSIGNED:
@@ -121,13 +122,13 @@
break;
}
- switch (config.proxySettings) {
+ switch (config.getProxySettings()) {
case STATIC:
- ProxyInfo proxyProperties = config.httpProxy;
+ ProxyInfo proxyProperties = config.getHttpProxy();
String exclusionList = ProxyUtils.exclusionListAsString(
proxyProperties.getExclusionList());
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
out.writeUTF(PROXY_HOST_KEY);
out.writeUTF(proxyProperties.getHost());
out.writeUTF(PROXY_PORT_KEY);
@@ -139,16 +140,16 @@
written = true;
break;
case PAC:
- ProxyInfo proxyPacProperties = config.httpProxy;
+ ProxyInfo proxyPacProperties = config.getHttpProxy();
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
out.writeUTF(PROXY_PAC_FILE);
out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
written = true;
break;
case NONE:
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
written = true;
break;
case UNASSIGNED:
@@ -267,11 +268,14 @@
IpAssignment ipAssignment = IpAssignment.DHCP;
ProxySettings proxySettings = ProxySettings.NONE;
StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+ LinkAddress linkAddress = null;
+ InetAddress gatewayAddress = null;
String proxyHost = null;
String pacFileUrl = null;
int proxyPort = -1;
String exclusionList = null;
String key;
+ final List<InetAddress> dnsServers = new ArrayList<>();
do {
key = in.readUTF();
@@ -286,15 +290,15 @@
} else if (key.equals(IP_ASSIGNMENT_KEY)) {
ipAssignment = IpAssignment.valueOf(in.readUTF());
} else if (key.equals(LINK_ADDRESS_KEY)) {
- LinkAddress linkAddr =
+ LinkAddress parsedLinkAddress =
new LinkAddress(
InetAddresses.parseNumericAddress(in.readUTF()),
in.readInt());
- if (linkAddr.getAddress() instanceof Inet4Address &&
- staticIpConfiguration.ipAddress == null) {
- staticIpConfiguration.ipAddress = linkAddr;
+ if (parsedLinkAddress.getAddress() instanceof Inet4Address
+ && linkAddress == null) {
+ linkAddress = parsedLinkAddress;
} else {
- loge("Non-IPv4 or duplicate address: " + linkAddr);
+ loge("Non-IPv4 or duplicate address: " + parsedLinkAddress);
}
} else if (key.equals(GATEWAY_KEY)) {
LinkAddress dest = null;
@@ -302,8 +306,8 @@
if (version == 1) {
// only supported default gateways - leave the dest/prefix empty
gateway = InetAddresses.parseNumericAddress(in.readUTF());
- if (staticIpConfiguration.gateway == null) {
- staticIpConfiguration.gateway = gateway;
+ if (gatewayAddress == null) {
+ gatewayAddress = gateway;
} else {
loge("Duplicate gateway: " + gateway.getHostAddress());
}
@@ -317,17 +321,18 @@
if (in.readInt() == 1) {
gateway = InetAddresses.parseNumericAddress(in.readUTF());
}
- RouteInfo route = new RouteInfo(dest, gateway);
- if (route.isIPv4Default() &&
- staticIpConfiguration.gateway == null) {
- staticIpConfiguration.gateway = gateway;
+ // If the destination is a default IPv4 route, use the gateway
+ // address unless already set.
+ if (dest.getAddress() instanceof Inet4Address
+ && dest.getPrefixLength() == 0 && gatewayAddress == null) {
+ gatewayAddress = gateway;
} else {
- loge("Non-IPv4 default or duplicate route: " + route);
+ loge("Non-IPv4 default or duplicate route: "
+ + dest.getAddress());
}
}
} else if (key.equals(DNS_KEY)) {
- staticIpConfiguration.dnsServers.add(
- InetAddresses.parseNumericAddress(in.readUTF()));
+ dnsServers.add(InetAddresses.parseNumericAddress(in.readUTF()));
} else if (key.equals(PROXY_SETTINGS_KEY)) {
proxySettings = ProxySettings.valueOf(in.readUTF());
} else if (key.equals(PROXY_HOST_KEY)) {
@@ -348,25 +353,31 @@
}
} while (true);
+ staticIpConfiguration = new StaticIpConfiguration.Builder()
+ .setIpAddress(linkAddress)
+ .setGateway(gatewayAddress)
+ .setDnsServers(dnsServers)
+ .build();
+
if (uniqueToken != null) {
IpConfiguration config = new IpConfiguration();
networks.put(uniqueToken, config);
switch (ipAssignment) {
case STATIC:
- config.staticIpConfiguration = staticIpConfiguration;
- config.ipAssignment = ipAssignment;
+ config.setStaticIpConfiguration(staticIpConfiguration);
+ config.setIpAssignment(ipAssignment);
break;
case DHCP:
- config.ipAssignment = ipAssignment;
+ config.setIpAssignment(ipAssignment);
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED IP on file, use DHCP");
- config.ipAssignment = IpAssignment.DHCP;
+ config.setIpAssignment(IpAssignment.DHCP);
break;
default:
loge("Ignore invalid ip assignment while reading.");
- config.ipAssignment = IpAssignment.UNASSIGNED;
+ config.setIpAssignment(IpAssignment.UNASSIGNED);
break;
}
@@ -374,25 +385,25 @@
case STATIC:
ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
ProxyUtils.exclusionStringAsList(exclusionList));
- config.proxySettings = proxySettings;
- config.httpProxy = proxyInfo;
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyInfo);
break;
case PAC:
ProxyInfo proxyPacProperties =
ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
- config.proxySettings = proxySettings;
- config.httpProxy = proxyPacProperties;
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyPacProperties);
break;
case NONE:
- config.proxySettings = proxySettings;
+ config.setProxySettings(proxySettings);
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED proxy on file, use NONE");
- config.proxySettings = ProxySettings.NONE;
+ config.setProxySettings(ProxySettings.NONE);
break;
default:
loge("Ignore invalid proxy settings while reading");
- config.proxySettings = ProxySettings.UNASSIGNED;
+ config.setProxySettings(ProxySettings.UNASSIGNED);
break;
}
} else {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d649925..16eac91 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -58,7 +58,9 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_MASK;
+import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM;
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_USER_EXEMPTED;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
@@ -4624,8 +4626,8 @@
newBlockedReasons |= (mRestrictBackground ? BLOCKED_METERED_REASON_DATA_SAVER : 0);
newBlockedReasons |= (isDenied ? BLOCKED_METERED_REASON_USER_RESTRICTED : 0);
- newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
- newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
+ newAllowedReasons |= (isSystem(uid) ? ALLOWED_METERED_REASON_SYSTEM : 0);
+ newAllowedReasons |= (isForeground ? ALLOWED_METERED_REASON_FOREGROUND : 0);
newAllowedReasons |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0);
if (LOGV) {
@@ -4699,18 +4701,18 @@
// Dispatch changed rule to existing listeners.
mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
+ }
- final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
- uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
- & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
- uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
- & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
- uidBlockedState.updateEffectiveBlockedReasons();
- if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
- mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
- uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
- .sendToTarget();
- }
+ final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+ uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+ & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+ uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+ & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+ uidBlockedState.updateEffectiveBlockedReasons();
+ if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
+ mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
+ uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
+ .sendToTarget();
}
}
@@ -5858,12 +5860,17 @@
return;
}
if ((allowedReasons & ALLOWED_REASON_SYSTEM) != 0) {
- effectiveBlockedReasons = BLOCKED_REASON_NONE;
+ effectiveBlockedReasons = (blockedReasons & ALLOWED_METERED_REASON_MASK);
+ }
+ if ((allowedReasons & ALLOWED_METERED_REASON_SYSTEM) != 0) {
+ effectiveBlockedReasons = (blockedReasons & ~ALLOWED_METERED_REASON_MASK);
}
if ((allowedReasons & ALLOWED_REASON_FOREGROUND) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ }
+ if ((allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 68fc14c..08dbd77 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7490,9 +7490,11 @@
boolean rateLimitingEnabled =
!mToastRateLimitingDisabledUids.contains(record.uid);
boolean isWithinQuota =
- mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
+ mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG)
+ || isExemptFromRateLimiting(record.pkg, userId);
- if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
+ if (tryShowToast(
+ record, rateLimitingEnabled, isWithinQuota)) {
scheduleDurationReachedLocked(record, lastToastWasTextRecord);
mIsCurrentToastShown = true;
if (rateLimitingEnabled) {
@@ -7526,6 +7528,18 @@
return record.show();
}
+ private boolean isExemptFromRateLimiting(String pkg, int userId) {
+ boolean isExemptFromRateLimiting = false;
+ try {
+ isExemptFromRateLimiting = mPackageManager.checkPermission(
+ android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
+ == PackageManager.PERMISSION_GRANTED;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to connect with package manager");
+ }
+ return isExemptFromRateLimiting;
+ }
+
/** Reports rate limiting toasts compat change (used when the toast was blocked). */
private void reportCompatRateLimitingToastsChange(int uid) {
final long id = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9c4c510..cc6a824 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -411,8 +411,13 @@
processName = stream.readString(Tombstone.PROCESS_NAME);
break;
- case (int) Tombstone.CAUSE:
- long token = stream.start(Tombstone.CAUSE);
+ case (int) Tombstone.CAUSES:
+ if (!crashReason.equals("")) {
+ // Causes appear in decreasing order of likelihood. For now we only
+ // want the most likely crash reason here, so ignore all others.
+ break;
+ }
+ long token = stream.start(Tombstone.CAUSES);
cause:
while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (stream.getFieldNumber()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 43eeb2a5..b27c0bd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -22903,9 +22903,14 @@
@Override
public String getWellbeingPackageName() {
- return CollectionUtils.firstOrNull(
- mContext.getSystemService(RoleManager.class).getRoleHolders(
- RoleManager.ROLE_SYSTEM_WELLBEING));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return CollectionUtils.firstOrNull(
+ mContext.getSystemService(RoleManager.class).getRoleHolders(
+ RoleManager.ROLE_SYSTEM_WELLBEING));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 302e657..8c3c423 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,8 +20,17 @@
import android.annotation.UserIdInt;
import android.app.Person;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+import android.app.appsearch.ReportUsageRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.content.ComponentName;
import android.content.Intent;
@@ -36,6 +45,7 @@
import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.PersistableBundle;
+import android.os.StrictMode;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -48,6 +58,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -70,13 +81,15 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -145,9 +158,9 @@
private static final String KEY_BITMAP_BYTES = "bitmapBytes";
/**
- * All the shortcuts from the package, keyed on IDs.
+ * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
- final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+ final ArraySet<ShortcutInfo> mShortcuts = new ArraySet<>();
/**
* All the share targets from the package
@@ -207,7 +220,9 @@
}
public int getShortcutCount() {
- return mShortcuts.size();
+ final int[] count = new int[1];
+ forEachShortcut(si -> count[0]++);
+ return count[0];
}
@Override
@@ -221,17 +236,20 @@
// - Unshadow all shortcuts.
// - Set disabled reason.
// - Disable if needed.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_SHADOW);
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasFlags(ShortcutInfo.FLAG_SHADOW)
+ && si.getDisabledReason() == restoreBlockReason
+ && restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ return false;
+ }
+ si.clearFlags(ShortcutInfo.FLAG_SHADOW);
- shortcut.setDisabledReason(restoreBlockReason);
- if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
- shortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
- }
- });
- }
+ si.setDisabledReason(restoreBlockReason);
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.addFlags(ShortcutInfo.FLAG_DISABLED);
+ }
+ return true;
+ });
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags();
@@ -242,7 +260,7 @@
*/
@Nullable
public ShortcutInfo findShortcutById(String id) {
- return mShortcuts.get(id);
+ return getShortcutById(id);
}
public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -265,7 +283,7 @@
}
public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
- ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
+ ensureNotImmutable(findShortcutById(id), ignoreInvisible);
}
public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
@@ -308,8 +326,9 @@
* Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
+ final ShortcutInfo shortcut = getShortcutById(id);
if (shortcut != null) {
+ removeShortcut(id);
mShortcutUser.mService.removeIconLocked(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
| ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
@@ -326,10 +345,10 @@
forceDeleteShortcutInner(newShortcut.getId());
- // Extract Icon and update the icon res ID and the bitmap path.
s.saveIconAndFixUpShortcutLocked(newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
- mShortcuts.put(newShortcut.getId(), newShortcut);
+
+ saveShortcut(newShortcut);
}
/**
@@ -347,7 +366,7 @@
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
if (oldShortcut != null) {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
@@ -379,7 +398,7 @@
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
changedShortcuts.clear();
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
boolean deleted = false;
if (oldShortcut == null) {
@@ -418,6 +437,16 @@
}
forceReplaceShortcutInner(newShortcut);
+ // TODO: Report usage can be filed async
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.reportUsage(
+ new ReportUsageRequest.Builder(getPackageName())
+ .setUri(newShortcut.getId()).build(),
+ mShortcutUser.mExecutor, result -> future.complete(result.isSuccess()));
+ return future;
+ });
+
return deleted;
}
@@ -427,19 +456,12 @@
* @return List of removed shortcuts.
*/
private List<ShortcutInfo> removeOrphans() {
- List<ShortcutInfo> removeList = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.isAlive()) continue;
-
- if (removeList == null) {
- removeList = new ArrayList<>();
- }
+ final List<ShortcutInfo> removeList = new ArrayList<>(1);
+ forEachShortcut(si -> {
+ if (si.isAlive()) return;
removeList.add(si);
- }
- if (removeList != null) {
+ });
+ if (!removeList.isEmpty()) {
for (int i = removeList.size() - 1; i >= 0; i--) {
forceDeleteShortcutInner(removeList.get(i).getId());
}
@@ -456,20 +478,19 @@
public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
- boolean changed = false;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final boolean[] changed = new boolean[1];
+ forEachShortcutMutateIf(si -> {
if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
- changed = true;
+ changed[0] = true;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- shortcut.setRank(0); // It may still be pinned, so clear the rank.
- });
+ si.setTimestamp(now);
+ si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ si.setRank(0); // It may still be pinned, so clear the rank.
+ return true;
}
- }
- if (changed) {
+ return false;
+ });
+ if (changed[0]) {
return removeOrphans();
}
return null;
@@ -508,7 +529,7 @@
* @return The deleted shortcut, or null if it was not actually removed because it's pinned.
*/
public ShortcutInfo deleteLongLivedWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
- final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
+ final ShortcutInfo shortcut = findShortcutById(shortcutId);
if (shortcut != null) {
mutateShortcut(shortcutId, null, si -> si.clearFlags(ShortcutInfo.FLAG_CACHED_ALL));
}
@@ -551,7 +572,7 @@
Preconditions.checkState(
(disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
"disable and disabledReason disagree: " + disable + " vs " + disabledReason);
- final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
+ final ShortcutInfo oldShortcut = findShortcutById(shortcutId);
if (oldShortcut == null || !oldShortcut.isEnabled()
&& (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
@@ -567,7 +588,7 @@
si.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
si.addFlags(ShortcutInfo.FLAG_DISABLED);
- // Do not overwrite the disabled reason if one is alreay set.
+ // Do not overwrite the disabled reason if one is already set.
if (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
si.setDisabledReason(disabledReason);
}
@@ -579,7 +600,6 @@
si.setActivity(null);
}
});
-
return null;
} else {
forceDeleteShortcutInner(shortcutId);
@@ -596,7 +616,7 @@
}
public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
- final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+ final ShortcutInfo source = findShortcutById(shortcut.getId());
Objects.requireNonNull(source);
mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
@@ -615,13 +635,6 @@
* <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
*/
public void refreshPinnedFlags() {
- // TODO: rewrite this function with proper query (i.e. fetch only pinned shortcuts and
- // unpin if it's no longer pinned by any launcher and vice versa)
- final List<ShortcutInfo> shortcuts = new ArrayList<>(mShortcuts.values());
- final Map<String, ShortcutInfo> shortcutMap = new ArrayMap<>(shortcuts.size());
- for (ShortcutInfo si : shortcuts) {
- shortcutMap.put(si.getId(), si);
- }
final Set<String> pinnedShortcuts = new ArraySet<>();
// First, for the pinned set for each launcher, keep track of their id one by one.
@@ -631,31 +644,20 @@
if (pinned == null || pinned.size() == 0) {
return;
}
- for (int i = pinned.size() - 1; i >= 0; i--) {
- final String id = pinned.valueAt(i);
- final ShortcutInfo si = shortcutMap.get(id);
- if (si == null) {
- // This happens if a launcher pinned shortcuts from this package, then backup&
- // restored, but this package doesn't allow backing up.
- // In that case the launcher ends up having a dangling pinned shortcuts.
- // That's fine, when the launcher is restored, we'll fix it.
- continue;
- }
- pinnedShortcuts.add(si.getId());
- }
+ pinnedShortcuts.addAll(pinned);
});
// Then, update the pinned state if necessary
- for (int i = shortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = shortcuts.get(i);
+ forEachShortcutMutateIf(si -> {
if (pinnedShortcuts.contains(si.getId()) && !si.isPinned()) {
- mutateShortcut(si.getId(), si,
- shortcut -> shortcut.addFlags(ShortcutInfo.FLAG_PINNED));
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) {
- mutateShortcut(si.getId(), si, shortcut ->
- shortcut.clearFlags(ShortcutInfo.FLAG_PINNED));
+ si.clearFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
- }
+ return false;
+ });
// Lastly, remove the ones that are no longer pinned, cached nor dynamic.
removeOrphans();
@@ -764,17 +766,13 @@
// Restored and the app not installed yet, so don't return any.
return;
}
-
final ShortcutService s = mShortcutUser.mService;
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
- .getPinnedShortcutIds(getPackageName(), getPackageUserId());
-
- for (int i = 0; i < mShortcuts.size(); i++) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ .getPinnedShortcutIds(getPackageName(), getPackageUserId());
+ forEachShortcut(si -> {
// Need to adjust PINNED flag depending on the caller.
// Basically if the caller is a launcher (callingLauncher != null) and the launcher
// isn't pinning it, then we need to clear PINNED for this caller.
@@ -784,7 +782,7 @@
if (!getPinnedByAnyLauncher) {
if (si.isFloating() && !si.isCached()) {
if (!isPinnedByCaller) {
- continue;
+ return;
}
}
}
@@ -804,7 +802,7 @@
}
result.add(clone);
}
- }
+ });
}
public void resetThrottling() {
@@ -874,7 +872,7 @@
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) {
+ if (getShortcutCount() == 0 || mShareTargets.isEmpty()) {
return 0;
}
@@ -912,14 +910,12 @@
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
public ArraySet<String> getUsedBitmapFiles() {
- final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final ArraySet<String> usedFiles = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.getBitmapPath() != null) {
usedFiles.add(getFileName(si.getBitmapPath()));
}
- }
+ });
return usedFiles;
}
@@ -936,30 +932,29 @@
* @return false if any of the target activities are no longer enabled.
*/
private boolean areAllActivitiesStillEnabled() {
- if (mShortcuts.size() == 0) {
- return true;
- }
final ShortcutService s = mShortcutUser.mService;
// Normally the number of target activities is 1 or so, so no need to use a complex
// structure like a set.
final ArrayList<ComponentName> checked = new ArrayList<>(4);
+ final boolean[] reject = new boolean[1];
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutStopWhen(si -> {
final ComponentName activity = si.getActivity();
if (checked.contains(activity)) {
- continue; // Already checked.
+ return false; // Already checked.
}
checked.add(activity);
if ((activity != null)
&& !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
- return false;
+ reject[0] = true;
+ return true; // Found at least 1 activity is disabled, so skip the rest.
}
- }
- return true;
+ return false;
+ });
+ return !reject[0];
}
/**
@@ -1042,33 +1037,32 @@
// See if there are any shortcuts that were prevented restoring because the app was of a
// lower version, and re-enable them.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
- continue;
+ return false;
}
if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
si.getId(), getPackageInfo().getBackupSourceVersionCode()));
}
- continue;
+ return false;
}
Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
- shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
- });
- }
+ if (si.hasFlags(ShortcutInfo.FLAG_DISABLED)
+ || si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+ return true;
+ }
+ return false;
+ });
// For existing shortcuts, update timestamps if they have any resources.
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
if (!isNewApp) {
- Resources publisherRes = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ final Resources publisherRes = getPackageResources();
+ forEachShortcutMutateIf(si -> {
// Disable dynamic shortcuts whose target activity is gone.
if (si.isDynamic()) {
if (si.getActivity() == null) {
@@ -1081,33 +1075,26 @@
getPackageName(), si.getId()));
if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
ShortcutInfo.DISABLED_REASON_APP_CHANGED) != null) {
- continue; // Actually removed.
+ return false; // Actually removed.
}
// Still pinned, so fall-through and possibly update the resources.
}
}
- if (si.hasAnyResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- if (!shortcut.isOriginallyFromManifest()) {
- shortcut.lookupAndFillInResourceIds(res);
- }
-
- // If this shortcut is not from a manifest, then update all resource IDs
- // from resource names. (We don't allow resource strings for
- // non-manifest at the moment, but icons can still be resources.)
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
+ if (!si.hasAnyResources() || publisherRes == null) {
+ return false;
}
- }
+
+ if (!si.isOriginallyFromManifest()) {
+ si.lookupAndFillInResourceIds(publisherRes);
+ }
+
+ // If this shortcut is not from a manifest, then update all resource IDs
+ // from resource names. (We don't allow resource strings for
+ // non-manifest at the moment, but icons can still be resources.)
+ si.setTimestamp(s.injectCurrentTimeMillis());
+ return true;
+ });
}
// (Re-)publish manifest shortcut.
@@ -1133,17 +1120,12 @@
boolean changed = false;
// Keep the previous IDs.
- ArraySet<String> toDisableList = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ final ArraySet<String> toDisableList = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.isManifestShortcut()) {
- if (toDisableList == null) {
- toDisableList = new ArraySet<>();
- }
toDisableList.add(si.getId());
}
- }
+ });
// Publish new ones.
if (newManifestShortcutList != null) {
@@ -1156,7 +1138,7 @@
final boolean newDisabled = !newShortcut.isEnabled();
final String id = newShortcut.getId();
- final ShortcutInfo oldShortcut = mShortcuts.get(id);
+ final ShortcutInfo oldShortcut = findShortcutById(id);
boolean wasPinned = false;
@@ -1183,7 +1165,7 @@
// regardless.
forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
- if (!newDisabled && toDisableList != null) {
+ if (!newDisabled && !toDisableList.isEmpty()) {
// Still alive, don't remove.
toDisableList.remove(id);
}
@@ -1191,7 +1173,7 @@
}
// Disable the previous manifest shortcuts that are no longer in the manifest.
- if (toDisableList != null) {
+ if (!toDisableList.isEmpty()) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format(
"Package %s: disabling %d stale shortcuts", getPackageName(),
@@ -1206,8 +1188,9 @@
/* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
ShortcutInfo.DISABLED_REASON_APP_CHANGED);
}
- removeOrphans();
}
+ removeOrphans();
+
adjustRanks();
return changed;
}
@@ -1279,25 +1262,21 @@
private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
= new ArrayMap<>();
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (si.isFloating()) {
- continue; // Ignore floating shortcuts, which are not tied to any activities.
+ return; // Ignore floating shortcuts, which are not tied to any activities.
}
final ComponentName activity = si.getActivity();
if (activity == null) {
mShortcutUser.mService.wtf("null activity detected.");
- continue;
+ return;
}
- ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
- if (list == null) {
- list = new ArrayList<>();
- activitiesToShortcuts.put(activity, list);
- }
+ ArrayList<ShortcutInfo> list = activitiesToShortcuts.computeIfAbsent(activity,
+ k -> new ArrayList<>());
list.add(si);
- }
+ });
return activitiesToShortcuts;
}
@@ -1333,15 +1312,13 @@
// (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
// anyway.)
final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo shortcut = mShortcuts.valueAt(i);
-
+ forEachShortcut(shortcut -> {
if (shortcut.isManifestShortcut()) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
} else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
}
- }
+ });
for (int i = newList.size() - 1; i >= 0; i--) {
final ShortcutInfo newShortcut = newList.get(i);
@@ -1354,7 +1331,7 @@
continue; // Activity can be null for update.
}
- final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo original = findShortcutById(newShortcut.getId());
if (original == null) {
if (operation == ShortcutService.OPERATION_UPDATE) {
continue; // When updating, ignore if there's no target.
@@ -1391,34 +1368,19 @@
* For all the text fields, refresh the string values if they're from resources.
*/
public void resolveResourceStrings() {
- // TODO: update resource strings in AppSearch
final ShortcutService s = mShortcutUser.mService;
- List<ShortcutInfo> changedShortcuts = null;
+ final Resources publisherRes = getPackageResources();
+ final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
- Resources publisherRes = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.hasStringResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.resolveResourceStrings(res);
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
-
- if (changedShortcuts == null) {
- changedShortcuts = new ArrayList<>(1);
- }
+ if (publisherRes != null) {
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasStringResources()) return false;
+ si.resolveResourceStrings(publisherRes);
+ si.setTimestamp(s.injectCurrentTimeMillis());
changedShortcuts.add(si);
- }
+ return true;
+ });
}
if (!CollectionUtils.isEmpty(changedShortcuts)) {
s.packageShortcutsChanged(getPackageName(), getPackageUserId(), changedShortcuts, null);
@@ -1427,10 +1389,7 @@
/** Clears the implicit ranks for all shortcuts. */
public void clearAllImplicitRanks() {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, ShortcutInfo::clearImplicitRankAndRankChangedFlag);
- }
+ forEachShortcutMutate(ShortcutInfo::clearImplicitRankAndRankChangedFlag);
}
/**
@@ -1470,17 +1429,16 @@
final long now = s.injectCurrentTimeMillis();
// First, clear ranks for floating shortcuts.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.isFloating()) {
if (si.getRank() != 0) {
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.setRank(0);
- });
+ si.setTimestamp(now);
+ si.setRank(0);
+ return true;
}
}
- }
+ return false;
+ });
// Then adjust ranks. Ranks are unique for each activity, so we first need to sort
// shortcuts to each activity.
@@ -1505,7 +1463,7 @@
}
// At this point, it must be dynamic.
if (!si.isDynamic()) {
- s.wtf("Non-dynamic shortcut found.");
+ s.wtf("Non-dynamic shortcut found.: " + si.toInsecureString());
continue;
}
final int thisRank = rank++;
@@ -1521,13 +1479,15 @@
/** @return true if there's any shortcuts that are not manifest shortcuts. */
public boolean hasNonManifestShortcuts() {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final boolean[] condition = new boolean[1];
+ forEachShortcutStopWhen(si -> {
if (!si.isDeclaredInManifest()) {
+ condition[0] = true;
return true;
}
- }
- return false;
+ return false;
+ });
+ return condition[0];
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
@@ -1567,11 +1527,8 @@
pw.print(prefix);
pw.println(" Shortcuts:");
- long totalBitmapSize = 0;
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ final long[] totalBitmapSize = new long[1];
+ forEachShortcut(si -> {
pw.println(si.toDumpString(prefix + " "));
if (si.getBitmapPath() != null) {
final long len = new File(si.getBitmapPath()).length();
@@ -1580,15 +1537,15 @@
pw.print("bitmap size=");
pw.println(len);
- totalBitmapSize += len;
+ totalBitmapSize[0] += len;
}
- }
+ });
pw.print(prefix);
pw.print(" ");
pw.print("Total bitmap size: ");
- pw.print(totalBitmapSize);
+ pw.print(totalBitmapSize[0]);
pw.print(" (");
- pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
+ pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
pw.println(")");
}
@@ -1603,46 +1560,39 @@
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
| (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ forEachShortcut(si -> {
if ((si.getFlags() & shortcutFlags) != 0) {
pw.println(si.toDumpString(""));
}
- }
+ });
}
@Override
public JSONObject dumpCheckin(boolean clear) throws JSONException {
final JSONObject result = super.dumpCheckin(clear);
- int numDynamic = 0;
- int numPinned = 0;
- int numManifest = 0;
- int numBitmaps = 0;
- long totalBitmapSize = 0;
+ final int[] numDynamic = new int[1];
+ final int[] numPinned = new int[1];
+ final int[] numManifest = new int[1];
+ final int[] numBitmaps = new int[1];
+ final long[] totalBitmapSize = new long[1];
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
-
- if (si.isDynamic()) numDynamic++;
- if (si.isDeclaredInManifest()) numManifest++;
- if (si.isPinned()) numPinned++;
+ forEachShortcut(si -> {
+ if (si.isDynamic()) numDynamic[0]++;
+ if (si.isDeclaredInManifest()) numManifest[0]++;
+ if (si.isPinned()) numPinned[0]++;
if (si.getBitmapPath() != null) {
- numBitmaps++;
- totalBitmapSize += new File(si.getBitmapPath()).length();
+ numBitmaps[0]++;
+ totalBitmapSize[0] += new File(si.getBitmapPath()).length();
}
- }
+ });
- result.put(KEY_DYNAMIC, numDynamic);
- result.put(KEY_MANIFEST, numManifest);
- result.put(KEY_PINNED, numPinned);
- result.put(KEY_BITMAPS, numBitmaps);
- result.put(KEY_BITMAP_BYTES, totalBitmapSize);
+ result.put(KEY_DYNAMIC, numDynamic[0]);
+ result.put(KEY_MANIFEST, numManifest[0]);
+ result.put(KEY_PINNED, numPinned[0]);
+ result.put(KEY_BITMAPS, numBitmaps[0]);
+ result.put(KEY_BITMAP_BYTES, totalBitmapSize[0]);
// TODO Log update frequency too.
@@ -1652,7 +1602,7 @@
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- final int size = mShortcuts.size();
+ final int size = getShortcutCount();
final int shareTargetSize = mShareTargets.size();
if (size == 0 && shareTargetSize == 0 && mApiCallCount == 0) {
@@ -1666,9 +1616,15 @@
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
- for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup,
- getPackageInfo().isBackupAllowed());
+ if (forBackup) {
+ // Shortcuts are persisted in AppSearch, xml is only needed for backup.
+ forEachShortcut(si -> {
+ try {
+ saveShortcut(out, si, forBackup, getPackageInfo().isBackupAllowed());
+ } catch (IOException | XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
if (!forBackup) {
@@ -1785,12 +1741,14 @@
}
final Intent[] intentsNoExtras = si.getIntentsNoExtras();
final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
- final int numIntents = intentsNoExtras.length;
- for (int i = 0; i < numIntents; i++) {
- out.startTag(null, TAG_INTENT);
- ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
- out.endTag(null, TAG_INTENT);
+ if (intentsNoExtras != null && intentsExtras != null) {
+ final int numIntents = intentsNoExtras.length;
+ for (int i = 0; i < numIntents; i++) {
+ out.startTag(null, TAG_INTENT);
+ ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+ out.endTag(null, TAG_INTENT);
+ }
}
ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
@@ -1877,9 +1835,8 @@
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName,
shortcutUser.getUserId(), fromBackup);
-
// Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
+ ret.mShortcuts.add(si);
continue;
case TAG_SHARE_TARGET:
ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
@@ -2075,7 +2032,9 @@
@VisibleForTesting
List<ShortcutInfo> getAllShortcutsForTest() {
- return new ArrayList<>(mShortcuts.values());
+ final List<ShortcutInfo> ret = new ArrayList<>(1);
+ forEachShortcut(ret::add);
+ return ret;
}
@VisibleForTesting
@@ -2087,7 +2046,7 @@
public void verifyStates() {
super.verifyStates();
- boolean failed = false;
+ final boolean[] failed = new boolean[1];
final ShortcutService s = mShortcutUser.mService;
@@ -2098,7 +2057,7 @@
for (int outer = all.size() - 1; outer >= 0; outer--) {
final ArrayList<ShortcutInfo> list = all.valueAt(outer);
if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
+ " has " + all.valueAt(outer).size() + " shortcuts.");
}
@@ -2118,61 +2077,60 @@
}
// Verify each shortcut's status.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned() || si.isCached())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not manifest, dynamic or pinned.");
}
if (si.isDeclaredInManifest() && si.isDynamic()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is both dynamic and manifest at the same time.");
}
if (si.getActivity() == null && !si.isFloating()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has null activity, but not floating.");
}
if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not floating, but is disabled.");
}
if (si.isFloating() && si.getRank() != 0) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is floating, but has rank=" + si.getRank());
}
if (si.getIcon() != null) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " still has an icon");
}
if (si.hasAdaptiveBitmap() && !(si.hasIconFile() || si.hasIconUri())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has adaptive bitmap but was not saved to a file nor has icon uri.");
}
if (si.hasIconFile() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both resource and bitmap icons");
}
if (si.hasIconFile() && si.hasIconUri()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and bitmap icons");
}
if (si.hasIconUri() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and resource icons");
}
if (si.isEnabled()
!= (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " isEnabled() and getDisabledReason() disagree: "
+ si.isEnabled() + " vs " + si.getDisabledReason());
@@ -2180,18 +2138,18 @@
if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
&& (getPackageInfo().getBackupSourceVersionCode()
== ShortcutInfo.VERSION_CODE_UNKNOWN)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " RESTORED_VERSION_LOWER with no backup source version code.");
}
if (s.isDummyMainActivity(si.getActivity())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has a dummy target activity");
}
- }
+ });
- if (failed) {
+ if (failed[0]) {
throw new IllegalStateException("See logcat for errors");
}
}
@@ -2202,7 +2160,7 @@
} else {
mPackageIdentifiers.remove(packageName);
}
- resetAppSearch(null);
+ resetAppSearch(session -> AndroidFuture.completedFuture(true));
}
void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
@@ -2212,23 +2170,282 @@
synchronized (mLock) {
if (shortcut != null) {
transform.accept(shortcut);
- } else {
- transform.accept(findShortcutById(id));
}
- // TODO: Load ShortcutInfo from AppSearch, apply transformation logic and save
+ final ShortcutInfo si = getShortcutById(id);
+ if (si == null) {
+ return;
+ }
+ transform.accept(si);
+ saveShortcut(si);
}
}
+ private void saveShortcut(@NonNull final ShortcutInfo... shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ saveShortcut(Arrays.asList(shortcuts));
+ }
+
+ private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ ConcurrentUtils.waitForFutureNoInterrupt(
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.put(new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ AppSearchShortcutInfo.toGenericDocuments(shortcuts))
+ .build(),
+ mShortcutUser.mExecutor,
+ result -> {
+ if (!result.isSuccess()) {
+ for (AppSearchResult<Void> k : result.getFailures().values()) {
+ Slog.e(TAG, k.getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "failed to save shortcuts"));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ }),
+ "saving shortcut");
+ }
+
/**
* Removes shortcuts from AppSearch.
*/
void removeShortcuts() {
+ awaitInAppSearch("removing shortcuts", session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove("",
+ new SearchSpec.Builder()
+ .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(new RuntimeException(
+ "Failed to cleanup shortcuts " + result.getErrorMessage()));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private void removeShortcut(@NonNull final String id) {
+ Objects.requireNonNull(id);
+ awaitInAppSearch("removing shortcut with id=" + id, session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove(new RemoveByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ final Map<String, AppSearchResult<Void>> failures =
+ result.getFailures();
+ for (String key : failures.keySet()) {
+ Slog.e(TAG, "Failed deleting " + key + ", error message:"
+ + failures.get(key).getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "Failed to delete shortcut: " + id));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private ShortcutInfo getShortcutById(String id) {
+ return awaitInAppSearch("getting shortcut with id=" + id, session -> {
+ final AndroidFuture<ShortcutInfo> future = new AndroidFuture<>();
+ session.getByUri(
+ new GetByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor,
+ results -> {
+ if (results.isSuccess()) {
+ Map<String, GenericDocument> documents = results.getSuccesses();
+ for (GenericDocument doc : documents.values()) {
+ final ShortcutInfo info = new AppSearchShortcutInfo(doc)
+ .toShortcutInfo(mShortcutUser.getUserId());
+ future.complete(info);
+ return;
+ }
+ }
+ future.complete(null);
+ });
+ return future;
+ });
+ }
+
+ private void forEachShortcut(
+ @NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutStopWhen(si -> {
+ cb.accept(si);
+ return false;
+ });
+ }
+
+ private void forEachShortcutMutate(@NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutMutateIf(si -> {
+ cb.accept(si);
+ return true;
+ });
+ }
+
+ private void forEachShortcutMutateIf(@NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("mutating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ final List<ShortcutInfo> changed = new ArrayList<>(1);
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) changed.add(si);
+ }
+ saveShortcut(changed);
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ forEachShortcutStopWhen("", cb);
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("iterating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search(query, new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) return;
+ }
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private List<ShortcutInfo> getNextPage(@NonNull final SearchResults res) {
+ final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
+ final List<ShortcutInfo> ret = new ArrayList<>();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ res.getNextPage(mShortcutUser.mExecutor, nextPage -> {
+ if (!nextPage.isSuccess()) {
+ future.complete(ret);
+ return;
+ }
+ final List<SearchResult> results = nextPage.getResultValue();
+ if (results.isEmpty()) {
+ future.complete(ret);
+ return;
+ }
+ final List<ShortcutInfo> page = new ArrayList<>(results.size());
+ for (SearchResult result : results) {
+ final ShortcutInfo si = new AppSearchShortcutInfo(result.getDocument())
+ .toShortcutInfo(mShortcutUser.getUserId());
+ page.add(si);
+ }
+ ret.addAll(page);
+ future.complete(ret);
+ });
+ return ConcurrentUtils.waitForFutureNoInterrupt(future,
+ "getting next batch of shortcuts");
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Nullable
+ private <T> T awaitInAppSearch(
+ @NonNull final String description,
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ return ConcurrentUtils.waitForFutureNoInterrupt(runInAppSearch(cb), description);
+ }
+
+ @Nullable
+ private <T> CompletableFuture<T> runInAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ synchronized (mLock) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
+ .build());
+ if (mAppSearchSession != null) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return AndroidFuture.supply(() -> mAppSearchSession).thenCompose(cb);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ } else {
+ return resetAppSearch(cb);
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+ }
+
+ private <T> CompletableFuture<T> resetAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ final AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder()
+ .setDatabaseName(getPackageName()).build();
+ final AppSearchSession session;
+ try {
+ session = ConcurrentUtils.waitForFutureNoInterrupt(
+ mShortcutUser.getAppSearch(searchContext), "resetting app search");
+ ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "setting up schema");
+ mAppSearchSession = session;
+ return cb.apply(mAppSearchSession);
+ } catch (Exception e) {
+ return AndroidFuture.completedFuture(null);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @NonNull
+ private AndroidFuture<AppSearchSession> setupSchema(
+ @NonNull final AppSearchSession session) {
+ SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
+ for (PackageIdentifier pi : mPackageIdentifiers.values()) {
+ schemaBuilder = schemaBuilder
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchPerson.SCHEMA_TYPE, true, pi)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
+ }
+ final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
+ session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new IllegalArgumentException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(session);
+ });
+ return future;
}
/**
* Merge/replace shortcuts parsed from xml file.
*/
void restoreParsedShortcuts(final boolean replace) {
+ if (replace) {
+ removeShortcuts();
+ }
+ saveShortcut(mShortcuts);
}
private boolean verifyRanksSequential(List<ShortcutInfo> list) {
@@ -2239,133 +2456,9 @@
if (si.getRank() != i) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
- + " rank=" + si.getRank() + " but expected to be "+ i);
+ + " rank=" + si.getRank() + " but expected to be " + i);
}
}
return failed;
}
-
- private void runInAppSearch(
- Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
- if (mShortcutUser == null) {
- Slog.w(TAG, "shortcut user is null");
- return;
- }
- synchronized (mLock) {
- if (mAppSearchSession != null) {
- final CountDownLatch latch = new CountDownLatch(1);
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- final SearchSessionObservable upstream =
- new SearchSessionObservable(mAppSearchSession, latch);
- for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
- : observers) {
- upstream.map(observer);
- }
- upstream.next();
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
- }
- ConcurrentUtils.waitForCountDownNoInterrupt(latch, 500,
- "timeout accessing shortcut");
- } else {
- resetAppSearch(observers);
- }
- }
- }
-
- private void resetAppSearch(
- Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
- final CountDownLatch latch = new CountDownLatch(1);
- final AppSearchManager.SearchContext searchContext =
- new AppSearchManager.SearchContext.Builder()
- .setDatabaseName(getPackageName()).build();
- mShortcutUser.runInAppSearch(searchContext, result -> {
- if (!result.isSuccess()) {
- Slog.e(TAG, "error getting search session during lazy init, "
- + result.getErrorMessage());
- latch.countDown();
- return;
- }
- // TODO: Flatten callback chain with proper async framework
- final SearchSessionObservable upstream =
- new SearchSessionObservable(result.getResultValue(), latch)
- .map(this::setupSchema);
- if (observers != null) {
- for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
- : observers) {
- upstream.map(observer);
- }
- }
- upstream.map(observable -> session -> {
- mAppSearchSession = session;
- observable.next();
- });
- upstream.next();
- });
- ConcurrentUtils.waitForCountDownNoInterrupt(latch, 1500,
- "timeout accessing shortcut during lazy initialization");
- }
-
- /**
- * creates the schema for shortcut in the database
- */
- private Consumer<AppSearchSession> setupSchema(SearchSessionObservable observable) {
- return session -> {
- SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
- .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
- for (PackageIdentifier pi : mPackageIdentifiers.values()) {
- schemaBuilder = schemaBuilder
- .setSchemaTypeVisibilityForPackage(
- AppSearchPerson.SCHEMA_TYPE, true, pi)
- .setSchemaTypeVisibilityForPackage(
- AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
- }
- session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
- if (!result.isSuccess()) {
- observable.error("failed to instantiate app search schema: "
- + result.getErrorMessage());
- return;
- }
- observable.next();
- });
- };
- }
-
- /**
- * TODO: Replace this temporary implementation with proper async framework
- */
- private class SearchSessionObservable {
-
- final AppSearchSession mSession;
- final CountDownLatch mLatch;
- final ArrayList<Consumer<AppSearchSession>> mObservers = new ArrayList<>(1);
-
- SearchSessionObservable(@NonNull final AppSearchSession session,
- @NonNull final CountDownLatch latch) {
- mSession = session;
- mLatch = latch;
- }
-
- SearchSessionObservable map(
- Function<SearchSessionObservable, Consumer<AppSearchSession>> observer) {
- mObservers.add(observer.apply(this));
- return this;
- }
-
- void next() {
- if (mObservers.isEmpty()) {
- mLatch.countDown();
- return;
- }
- mObservers.remove(0).accept(mSession);
- }
-
- void error(@Nullable final String errorMessage) {
- if (errorMessage != null) {
- Slog.e(TAG, errorMessage);
- }
- mLatch.countDown();
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 38cba4c..fc88f39 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2590,7 +2590,6 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.findAll(ret, query, cloneFlags);
-
return new ParceledListSlice<>(setReturnedByServer(ret));
}
@@ -5078,6 +5077,17 @@
}
@VisibleForTesting
+ void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ synchronized (mLock) {
+ final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
+ if (pkg == null) return;
+
+ pkg.mutateShortcut(shortcutId, null, cb);
+ }
+ }
+
+ @VisibleForTesting
ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
synchronized (mLock) {
final ShortcutUser user = mUsers.get(userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index ec784d0..51cb995 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
import android.content.pm.ShortcutManager;
import android.metrics.LogMaker;
@@ -35,6 +34,7 @@
import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.FgThread;
@@ -715,17 +715,26 @@
.setSubtype(totalSharingShortcutCount));
}
- void runInAppSearch(@NonNull final AppSearchManager.SearchContext searchContext,
- @NonNull final Consumer<AppSearchResult<AppSearchSession>> callback) {
+ AndroidFuture<AppSearchSession> getAppSearch(
+ @NonNull final AppSearchManager.SearchContext searchContext) {
+ final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
if (mAppSearchManager == null) {
- Slog.e(TAG, "app search manager is null");
- return;
+ future.completeExceptionally(new RuntimeException("app search manager is null"));
+ return future;
}
final long callingIdentity = Binder.clearCallingIdentity();
try {
- mAppSearchManager.createSearchSession(searchContext, mExecutor, callback);
+ mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new RuntimeException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(result.getResultValue());
+ });
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
+ return future;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fe19956..2a0257d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1507,6 +1507,26 @@
}
@Override
+ public boolean isCloneProfile(@UserIdInt int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile");
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ return userInfo != null && userInfo.isCloneProfile();
+ }
+ }
+
+ @Override
+ public boolean sharesMediaWithParent(@UserIdInt int userId) {
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+ "sharesMediaWithParent");
+ synchronized (mUsersLock) {
+ UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
+ return userTypeDetails != null ? userTypeDetails.isProfile()
+ && userTypeDetails.sharesMediaWithParent() : false;
+ }
+ }
+
+ @Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
"isUserUnlockingOrUnlocked");
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 17ce386..6824f7d 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -149,6 +149,13 @@
*/
private final @Nullable int[] mDarkThemeBadgeColors;
+ /**
+ * Denotes if the user shares media with its parent user.
+ *
+ * <p> Default value is false
+ */
+ private final boolean mSharesMediaWithParent;
+
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -158,7 +165,8 @@
@Nullable Bundle defaultRestrictions,
@Nullable Bundle defaultSystemSettings,
@Nullable Bundle defaultSecureSettings,
- @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters) {
+ @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
+ boolean sharesMediaWithParent) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -177,6 +185,7 @@
this.mBadgeLabels = badgeLabels;
this.mBadgeColors = badgeColors;
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
+ this.mSharesMediaWithParent = sharesMediaWithParent;
}
/**
@@ -291,6 +300,13 @@
return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
}
+ /**
+ * Returns true if the user has shared media with parent user or false otherwise.
+ */
+ public boolean sharesMediaWithParent() {
+ return mSharesMediaWithParent;
+ }
+
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -318,7 +334,6 @@
: Collections.emptyList();
}
-
/** Dumps details of the UserTypeDetails. Do not parse this. */
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mName: "); pw.println(mName);
@@ -383,6 +398,7 @@
private @DrawableRes int mIconBadge = Resources.ID_NULL;
private @DrawableRes int mBadgePlain = Resources.ID_NULL;
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
+ private boolean mSharesMediaWithParent = false;
public Builder setName(String name) {
mName = name;
@@ -473,6 +489,15 @@
return this;
}
+ /**
+ * Sets shared media property for the user.
+ * @param sharesMediaWithParent the value to be set, true or false
+ */
+ public Builder setSharesMediaWithParent(boolean sharesMediaWithParent) {
+ mSharesMediaWithParent = sharesMediaWithParent;
+ return this;
+ }
+
@UserInfoFlag int getBaseType() {
return mBaseType;
}
@@ -502,7 +527,7 @@
mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors,
mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors,
mDefaultRestrictions, mDefaultSystemSettings, mDefaultSecureSettings,
- mDefaultCrossProfileIntentFilters);
+ mDefaultCrossProfileIntentFilters, mSharesMediaWithParent);
}
private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 6aac0b2..e8421a5 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -29,6 +29,7 @@
import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
@@ -100,6 +101,7 @@
builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
+ builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
if (Build.IS_DEBUGGABLE) {
builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
}
@@ -108,6 +110,21 @@
}
/**
+ * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE}
+ * configuration.
+ */
+ // TODO(b/182396009): Add default restrictions, if needed for clone user type.
+ private static UserTypeDetails.Builder getDefaultTypeProfileClone() {
+ return new UserTypeDetails.Builder()
+ .setName(USER_TYPE_PROFILE_CLONE)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowedPerParent(1)
+ .setLabel(0)
+ .setDefaultRestrictions(null)
+ .setSharesMediaWithParent(true);
+ }
+
+ /**
* Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED}
* configuration.
*/
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index b421cfc..cda4806 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -435,11 +435,12 @@
}
}
}
+ boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
+ && !permission.isRuntime();
if (permission == null) {
permission = new Permission(permissionInfo.name, permissionInfo.packageName,
TYPE_MANIFEST);
}
- boolean wasNonRuntime = !permission.isRuntime();
StringBuilder r = null;
if (!permission.mReconciled) {
if (permission.mPermissionInfo.packageName == null
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 616058f..3bb5c16 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2937,7 +2937,8 @@
>= Build.VERSION_CODES.M;
for (String permission : ps.getGrantedPermissions()) {
- if (!pkg.getImplicitPermissions().contains(permission)) {
+ if (pkg.getRequestedPermissions().contains(permission)
+ && !pkg.getImplicitPermissions().contains(permission)) {
Permission bp = mRegistry.getPermission(permission);
if (bp != null && bp.isRuntime()) {
int flags = ps.getPermissionFlags(permission);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index cb3b5c9..44ff3eb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -22,7 +22,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Binder;
import com.android.internal.util.CollectionUtils;
import com.android.server.compat.PlatformCompat;
@@ -77,9 +76,7 @@
static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,
long changeId) {
- //noinspection ConstantConditions
- return Binder.withCleanCallingIdentity(
- () -> platformCompat.isChangeEnabled(changeId, buildMockAppInfo(pkg)));
+ return platformCompat.isChangeEnabledInternalNoLogging(changeId, buildMockAppInfo(pkg));
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d0aa28b..1b5bb95 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2341,8 +2341,6 @@
params.packageName = packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- params.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index a7f3cdb..c029bf5 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.hardware.SensorPrivacyManager;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ResultReceiver;
@@ -79,6 +80,7 @@
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__FAILURE;
private final Context mContext;
+ private final SensorPrivacyManager mPrivacyManager;
boolean mIsServiceEnabled;
public RotationResolverManagerService(Context context) {
@@ -89,6 +91,7 @@
PACKAGE_UPDATE_POLICY_REFRESH_EAGER
| /*To avoid high rotation latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
mContext = context;
+ mPrivacyManager = SensorPrivacyManager.getInstance(context);
}
@Override
@@ -156,15 +159,22 @@
Objects.requireNonNull(callbackInternal);
Objects.requireNonNull(cancellationSignalInternal);
synchronized (mLock) {
- if (mIsServiceEnabled) {
- final RotationResolverManagerPerUserService service = getServiceForUserLocked(
- UserHandle.getCallingUserId());
+ final boolean isCameraAvailable = !mPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.Sensors.CAMERA);
+ if (mIsServiceEnabled && isCameraAvailable) {
+ final RotationResolverManagerPerUserService service =
+ getServiceForUserLocked(
+ UserHandle.getCallingUserId());
final RotationResolutionRequest request = new RotationResolutionRequest("",
currentRotation, proposedRotation, true, timeout);
service.resolveRotationLocked(callbackInternal, request,
cancellationSignalInternal);
} else {
- Slog.w(TAG, "Rotation Resolver service is disabled.");
+ if (isCameraAvailable) {
+ Slog.w(TAG, "Rotation Resolver service is disabled.");
+ } else {
+ Slog.w(TAG, "Camera is locked by a toggle.");
+ }
callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
logRotationStats(proposedRotation, currentRotation, RESOLUTION_DISABLED);
}
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
new file mode 100644
index 0000000..3ca8a5a
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -0,0 +1,302 @@
+/*
+ * 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.servicewatcher;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import android.annotation.BoolRes;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Supplies services based on the current active user and version as defined in the service
+ * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
+ * ensure only system (ie, privileged) services are matched. It also handles services that are not
+ * direct boot aware, and will automatically pick the best service as the user's direct boot state
+ * changes.
+ *
+ * <p>Optionally, two permissions may be specified: (1) a caller permission - any service that does
+ * not require callers to hold this permission is rejected (2) a service permission - any service
+ * whose package does not hold this permission is rejected.
+ */
+public class CurrentUserServiceSupplier extends BroadcastReceiver implements
+ ServiceSupplier<CurrentUserServiceSupplier.BoundServiceInfo> {
+
+ private static final String TAG = "CurrentUserServiceSupplier";
+
+ private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+ private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+
+ private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
+ if (o1 == o2) {
+ return 0;
+ } else if (o1 == null) {
+ return -1;
+ } else if (o2 == null) {
+ return 1;
+ }
+
+ // ServiceInfos with higher version numbers always win. if version numbers are equal
+ // then we prefer components that work for all users vs components that only work for a
+ // single user at a time. otherwise everything's equal.
+ int ret = Integer.compare(o1.getVersion(), o2.getVersion());
+ if (ret == 0) {
+ if (o1.getUserId() != USER_SYSTEM && o2.getUserId() == USER_SYSTEM) {
+ ret = -1;
+ } else if (o1.getUserId() == USER_SYSTEM && o2.getUserId() != USER_SYSTEM) {
+ ret = 1;
+ }
+ }
+ return ret;
+ };
+
+ /** Bound service information with version information. */
+ public static class BoundServiceInfo extends ServiceWatcher.BoundServiceInfo {
+
+ private static int parseUid(ResolveInfo resolveInfo) {
+ int uid = resolveInfo.serviceInfo.applicationInfo.uid;
+ Bundle metadata = resolveInfo.serviceInfo.metaData;
+ if (metadata != null && metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false)) {
+ // reconstruct a uid for the same app but with the system user - hope this exists
+ uid = UserHandle.getUid(USER_SYSTEM, UserHandle.getAppId(uid));
+ }
+ return uid;
+ }
+
+ private static int parseVersion(ResolveInfo resolveInfo) {
+ int version = Integer.MIN_VALUE;
+ if (resolveInfo.serviceInfo.metaData != null) {
+ version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
+ }
+ return version;
+ }
+
+ private final int mVersion;
+ private final @Nullable Bundle mMetadata;
+
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(action, parseUid(resolveInfo), resolveInfo.serviceInfo.getComponentName(),
+ parseVersion(resolveInfo), resolveInfo.serviceInfo.metaData);
+ }
+
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName, int version,
+ @Nullable Bundle metadata) {
+ super(action, uid, componentName);
+
+ mVersion = version;
+ mMetadata = metadata;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public @Nullable Bundle getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "@" + mVersion;
+ }
+ }
+
+ private static @Nullable String retrieveExplicitPackage(Context context,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+ Resources resources = context.getResources();
+ boolean enableOverlay = resources.getBoolean(enableOverlayResId);
+ if (!enableOverlay) {
+ return resources.getString(nonOverlayPackageResId);
+ } else {
+ return null;
+ }
+ }
+
+ private final Context mContext;
+ private final ActivityManagerInternal mActivityManager;
+ private final Intent mIntent;
+ // a permission that the service forces callers (ie ServiceWatcher/system server) to hold
+ private final @Nullable String mCallerPermission;
+ // a permission that the service package should hold
+ private final @Nullable String mServicePermission;
+
+ private volatile ServiceChangedListener mListener;
+
+ public CurrentUserServiceSupplier(Context context, String action) {
+ this(context, action, null, null, null);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+ this(context, action,
+ retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId), null,
+ null);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
+ @Nullable String callerPermission, @Nullable String servicePermission) {
+ this(context, action,
+ retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId),
+ callerPermission, servicePermission);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @Nullable String explicitPackage, @Nullable String callerPermission,
+ @Nullable String servicePermission) {
+ mContext = context;
+ mActivityManager = Objects.requireNonNull(
+ LocalServices.getService(ActivityManagerInternal.class));
+ mIntent = new Intent(action);
+
+ if (explicitPackage != null) {
+ mIntent.setPackage(explicitPackage);
+ }
+
+ mCallerPermission = callerPermission;
+ mServicePermission = servicePermission;
+ }
+
+ @Override
+ public boolean hasMatchingService() {
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentServicesAsUser(mIntent,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
+ UserHandle.USER_SYSTEM);
+ return !resolveInfos.isEmpty();
+ }
+
+ @Override
+ public void register(ServiceChangedListener listener) {
+ Preconditions.checkState(mListener == null);
+
+ mListener = listener;
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiverAsUser(this, UserHandle.ALL, intentFilter, null,
+ FgThread.getHandler());
+ }
+
+ @Override
+ public void unregister() {
+ Preconditions.checkArgument(mListener != null);
+
+ mListener = null;
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public BoundServiceInfo getServiceInfo() {
+ BoundServiceInfo bestServiceInfo = null;
+
+ // only allow privileged services in the correct direct boot state to match
+ int currentUserId = mActivityManager.getCurrentUserId();
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
+ mIntent,
+ GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
+ currentUserId);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo service = Objects.requireNonNull(resolveInfo.serviceInfo);
+
+ if (mCallerPermission != null) {
+ if (!mCallerPermission.equals(service.permission)) {
+ Log.d(TAG, service.getComponentName().flattenToShortString()
+ + " disqualified due to not requiring " + mCallerPermission);
+ continue;
+ }
+ }
+
+ BoundServiceInfo serviceInfo = new BoundServiceInfo(mIntent.getAction(), resolveInfo);
+
+ if (mServicePermission != null) {
+ if (PermissionManager.checkPackageNamePermission(mServicePermission,
+ service.packageName, serviceInfo.getUserId()) != PERMISSION_GRANTED) {
+ Log.d(TAG, serviceInfo.getComponentName().flattenToShortString()
+ + " disqualified due to not holding " + mCallerPermission);
+ continue;
+ }
+ }
+
+ if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
+ bestServiceInfo = serviceInfo;
+ }
+ }
+
+ return bestServiceInfo;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId == UserHandle.USER_NULL) {
+ return;
+ }
+ ServiceChangedListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ switch (action) {
+ case Intent.ACTION_USER_SWITCHED:
+ listener.onServiceChanged();
+ break;
+ case Intent.ACTION_USER_UNLOCKED:
+ // user unlocked implies direct boot mode may have changed
+ if (userId == mActivityManager.getCurrentUserId()) {
+ listener.onServiceChanged();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 5d49663..23b5d98 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -16,516 +16,244 @@
package com.android.server.servicewatcher;
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-import static android.content.Context.BIND_NOT_VISIBLE;
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-import com.android.internal.annotations.Immutable;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.function.Predicate;
/**
- * Maintains a binding to the best service that matches the given intent information. Bind and
- * unbind callbacks, as well as all binder operations, will all be run on a single thread.
+ * A ServiceWatcher is responsible for continuously maintaining an active binding to a service
+ * selected by it's {@link ServiceSupplier}. The {@link ServiceSupplier} may change the service it
+ * selects over time, and the currently bound service may crash, restart, have a user change, have
+ * changes made to its package, and so on and so forth. The ServiceWatcher is responsible for
+ * maintaining the binding across all these changes.
+ *
+ * <p>Clients may invoke {@link BinderOperation}s on the ServiceWatcher, and it will make a best
+ * effort to run these on the currently bound service, but individual operations may fail (if there
+ * is no service currently bound for instance). In order to help clients maintain the correct state,
+ * clients may supply a {@link ServiceListener}, which is informed when the ServiceWatcher connects
+ * and disconnects from a service. This allows clients to bring a bound service back into a known
+ * state on connection, and then run binder operations from there. In order to help clients
+ * accomplish this, ServiceWatcher guarantees that {@link BinderOperation}s and the
+ * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
+ * can be established between them.
+ *
+ * There is never any guarantee of whether a ServiceWatcher is currently connected to a service, and
+ * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
+ * on this, and instead use {@link ServiceListener} notifications as necessary to recover from
+ * failures.
*/
-public class ServiceWatcher implements ServiceConnection {
+public interface ServiceWatcher {
- private static final String TAG = "ServiceWatcher";
- private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
- private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
-
- private static final long RETRY_DELAY_MS = 15 * 1000;
-
- private static final Predicate<ResolveInfo> DEFAULT_SERVICE_CHECK_PREDICATE = x -> true;
-
- /** Function to run on binder interface. */
- public interface BinderRunner {
- /** Called to run client code with the binder. */
+ /**
+ * Operation to run on a binder interface. All operations will be run on the thread used by the
+ * ServiceWatcher this is run with.
+ */
+ interface BinderOperation {
+ /** Invoked to run the operation. Run on the ServiceWatcher thread. */
void run(IBinder binder) throws RemoteException;
+
/**
- * Called if an error occurred and the function could not be run. This callback is only
- * intended for resource deallocation and cleanup in response to a single binder operation,
- * it should not be used to propagate errors further.
+ * Invoked if {@link #run(IBinder)} could not be invoked because there was no current
+ * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
+ * {@link RuntimeException}). This callback is only intended for resource deallocation and
+ * cleanup in response to a single binder operation, it should not be used to propagate
+ * errors further. Run on the ServiceWatcher thread.
*/
default void onError() {}
}
- /** Function to run on binder interface when first bound. */
- public interface OnBindRunner {
- /** Called to run client code with the binder. */
- void run(IBinder binder, BoundService service) throws RemoteException;
+ /**
+ * Listener for bind and unbind events. All operations will be run on the thread used by the
+ * ServiceWatcher this is run with.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
+ /** Invoked when a service is bound. Run on the ServiceWatcher thread. */
+ void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
+
+ /** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
+ void onUnbind();
}
/**
- * Information on the service ServiceWatcher has selected as the best option for binding.
+ * A listener for when a {@link ServiceSupplier} decides that the current service has changed.
*/
- @Immutable
- public static final class BoundService implements Comparable<BoundService> {
+ interface ServiceChangedListener {
+ /**
+ * Should be invoked when the current service may have changed.
+ */
+ void onServiceChanged();
+ }
- public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null,
- false, null, -1);
+ /**
+ * This supplier encapsulates the logic of deciding what service a {@link ServiceWatcher} should
+ * be bound to at any given moment.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceSupplier<TBoundServiceInfo extends BoundServiceInfo> {
+ /**
+ * Should return true if there exists at least one service capable of meeting the criteria
+ * of this supplier. This does not imply that {@link #getServiceInfo()} will always return a
+ * non-null result, as any service may be disqualified for various reasons at any point in
+ * time. May be invoked at any time from any thread and thus should generally not have any
+ * dependency on the other methods in this interface.
+ */
+ boolean hasMatchingService();
- public final int version;
- @Nullable
- public final ComponentName component;
- public final boolean serviceIsMultiuser;
- public final int uid;
- @Nullable
- public final Bundle metadata;
+ /**
+ * Invoked when the supplier should start monitoring for any changes that could result in a
+ * different service selection, and should invoke
+ * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
+ * may be invoked after this method is called.
+ */
+ void register(ServiceChangedListener listener);
- BoundService(ResolveInfo resolveInfo) {
- Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null);
+ /**
+ * Invoked when the supplier should stop monitoring for any changes that could result in a
+ * different service selection, should no longer invoke
+ * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
+ * invoked after this method is called.
+ */
+ void unregister();
- metadata = resolveInfo.serviceInfo.metaData;
- if (metadata != null) {
- version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
- serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
- } else {
- version = Integer.MIN_VALUE;
- serviceIsMultiuser = false;
- }
+ /**
+ * Must be implemented to return the current service selected by this supplier. May return
+ * null if no service currently meets the criteria. Only invoked while registered.
+ */
+ @Nullable TBoundServiceInfo getServiceInfo();
+ }
- component = resolveInfo.serviceInfo.getComponentName();
- uid = resolveInfo.serviceInfo.applicationInfo.uid;
+ /**
+ * Information on the service selected as the best option for binding.
+ */
+ class BoundServiceInfo {
+
+ protected final @Nullable String mAction;
+ protected final int mUid;
+ protected final ComponentName mComponentName;
+
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(action, resolveInfo.serviceInfo.applicationInfo.uid,
+ resolveInfo.serviceInfo.getComponentName());
}
- private BoundService(int version, @Nullable ComponentName component,
- boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) {
- Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE);
- this.version = version;
- this.component = component;
- this.serviceIsMultiuser = serviceIsMultiuser;
- this.metadata = metadata;
- this.uid = uid;
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
+ mAction = action;
+ mUid = uid;
+ mComponentName = Objects.requireNonNull(componentName);
}
- public @Nullable String getPackageName() {
- return component != null ? component.getPackageName() : null;
+ /** Returns the action associated with this bound service. */
+ public @Nullable String getAction() {
+ return mAction;
+ }
+
+ /** Returns the component of this bound service. */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /** Returns the user id for this bound service. */
+ public @UserIdInt int getUserId() {
+ return UserHandle.getUserId(mUid);
}
@Override
- public boolean equals(Object o) {
+ public final boolean equals(Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof BoundService)) {
+ if (o == null || getClass() != o.getClass()) {
return false;
}
- BoundService that = (BoundService) o;
- return version == that.version && uid == that.uid
- && Objects.equals(component, that.component);
+
+ BoundServiceInfo that = (BoundServiceInfo) o;
+ return mUid == that.mUid
+ && Objects.equals(mAction, that.mAction)
+ && mComponentName.equals(that.mComponentName);
}
@Override
- public int hashCode() {
- return Objects.hash(version, component, uid);
- }
-
- @Override
- public int compareTo(BoundService that) {
- // ServiceInfos with higher version numbers always win (having a version number >
- // MIN_VALUE implies having a non-null component). if version numbers are equal, a
- // non-null component wins over a null component. if the version numbers are equal and
- // both components exist then we prefer components that work for all users vs components
- // that only work for a single user at a time. otherwise everything's equal.
- int ret = Integer.compare(version, that.version);
- if (ret == 0) {
- if (component == null && that.component != null) {
- ret = -1;
- } else if (component != null && that.component == null) {
- ret = 1;
- } else {
- if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM
- && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) {
- ret = -1;
- } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM
- && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) {
- ret = 1;
- }
- }
- }
- return ret;
+ public final int hashCode() {
+ return Objects.hash(mAction, mUid, mComponentName);
}
@Override
public String toString() {
- if (component == null) {
+ if (mComponentName == null) {
return "none";
} else {
- return component.toShortString() + "@" + version + "[u"
- + UserHandle.getUserId(uid) + "]";
+ return mUid + "/" + mComponentName.flattenToShortString();
}
}
}
- private final Context mContext;
- private final Handler mHandler;
- private final Intent mIntent;
- private final Predicate<ResolveInfo> mServiceCheckPredicate;
-
- private final PackageMonitor mPackageMonitor = new PackageMonitor() {
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- return true;
- }
-
- @Override
- public void onSomePackagesChanged() {
- onBestServiceChanged(false);
- }
- };
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action == null) {
- return;
- }
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId == UserHandle.USER_NULL) {
- return;
- }
-
- switch (action) {
- case Intent.ACTION_USER_SWITCHED:
- onUserSwitched(userId);
- break;
- case Intent.ACTION_USER_UNLOCKED:
- onUserUnlocked(userId);
- break;
- default:
- break;
- }
-
- }
- };
-
- // read/write from handler thread only
- private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>();
-
- @Nullable
- private final OnBindRunner mOnBind;
-
- @Nullable
- private final Runnable mOnUnbind;
-
- // read/write from handler thread only
- private boolean mRegistered;
-
- // read/write from handler thread only
- private int mCurrentUserId;
-
- // write from handler thread only, read anywhere
- private volatile BoundService mTargetService;
- private volatile IBinder mBinder;
-
- public ServiceWatcher(Context context, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- this(context, FgThread.getHandler(), action, onBind, onUnbind, enableOverlayResId,
- nonOverlayPackageResId, DEFAULT_SERVICE_CHECK_PREDICATE);
- }
-
- public ServiceWatcher(Context context, Handler handler, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- this(context, handler, action, onBind, onUnbind, enableOverlayResId, nonOverlayPackageResId,
- DEFAULT_SERVICE_CHECK_PREDICATE);
- }
-
- public ServiceWatcher(Context context, Handler handler, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
- @NonNull Predicate<ResolveInfo> serviceCheckPredicate) {
- mContext = context;
- mHandler = handler;
- mIntent = new Intent(Objects.requireNonNull(action));
- mServiceCheckPredicate = Objects.requireNonNull(serviceCheckPredicate);
-
- Resources resources = context.getResources();
- boolean enableOverlay = resources.getBoolean(enableOverlayResId);
- if (!enableOverlay) {
- mIntent.setPackage(resources.getString(nonOverlayPackageResId));
- }
-
- mOnBind = onBind;
- mOnUnbind = onUnbind;
-
- mCurrentUserId = UserHandle.USER_NULL;
-
- mTargetService = BoundService.NONE;
- mBinder = null;
- }
-
- /**
- * Returns true if there is at least one component that could satisfy the ServiceWatcher's
- * constraints.
- */
- public boolean checkServiceResolves() {
- List<ResolveInfo> resolveInfos = mContext.getPackageManager()
- .queryIntentServicesAsUser(mIntent,
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
- UserHandle.USER_SYSTEM);
- for (ResolveInfo resolveInfo : resolveInfos) {
- if (mServiceCheckPredicate.test(resolveInfo)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Starts the process of determining the best matching service and maintaining a binding to it.
- */
- public void register() {
- mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal,
- ServiceWatcher.this));
- }
-
- private void registerInternal() {
- Preconditions.checkState(!mRegistered);
-
- mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
- intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null,
- mHandler);
-
- // TODO: This makes the behavior of the class unpredictable as the caller needs
- // to know the internal impl detail that calling register would pick the current user.
- mCurrentUserId = ActivityManager.getCurrentUser();
-
- mRegistered = true;
-
- mHandler.post(() -> onBestServiceChanged(false));
- }
-
- /**
- * Stops the process of determining the best matching service and releases any binding.
- */
- public void unregister() {
- mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal,
- ServiceWatcher.this));
- }
-
- private void unregisterInternal() {
- Preconditions.checkState(mRegistered);
-
- mRegistered = false;
-
- mPackageMonitor.unregister();
- mContext.unregisterReceiver(mBroadcastReceiver);
-
- mHandler.post(() -> onBestServiceChanged(false));
- }
-
- private void onBestServiceChanged(boolean forceRebind) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- BoundService bestServiceInfo = BoundService.NONE;
-
- if (mRegistered) {
- List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
- mIntent,
- GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
- mCurrentUserId);
- for (ResolveInfo resolveInfo : resolveInfos) {
- if (!mServiceCheckPredicate.test(resolveInfo)) {
- continue;
- }
- BoundService serviceInfo = new BoundService(resolveInfo);
- if (serviceInfo.compareTo(bestServiceInfo) > 0) {
- bestServiceInfo = serviceInfo;
- }
- }
- }
-
- if (forceRebind || !bestServiceInfo.equals(mTargetService)) {
- rebind(bestServiceInfo);
- }
- }
-
- private void rebind(BoundService newServiceInfo) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (!mTargetService.equals(BoundService.NONE)) {
- if (D) {
- Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
- }
-
- mContext.unbindService(this);
- onServiceDisconnected(mTargetService.component);
- mPendingBinds.remove(mTargetService.component);
- mTargetService = BoundService.NONE;
- }
-
- mTargetService = newServiceInfo;
- if (mTargetService.equals(BoundService.NONE)) {
- return;
- }
-
- Preconditions.checkState(mTargetService.component != null);
-
- Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
-
- Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
- if (!mContext.bindServiceAsUser(bindIntent, this,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
- mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) {
- mTargetService = BoundService.NONE;
- Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later");
- mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS);
- } else {
- mPendingBinds.put(mTargetService.component, mTargetService);
- }
- }
-
- @Override
- public final void onServiceConnected(ComponentName component, IBinder binder) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- Preconditions.checkState(mBinder == null);
-
- if (D) {
- Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
- }
-
- final BoundService boundService = mPendingBinds.remove(component);
- if (boundService == null) {
- return;
- }
-
- mBinder = binder;
- if (mOnBind != null) {
- try {
- mOnBind.run(binder, boundService);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, getLogPrefix() + " exception running on " + component, e);
- }
- }
- }
-
- @Override
- public final void onServiceDisconnected(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (mBinder == null) {
- return;
- }
-
- if (D) {
- Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
- }
-
- mBinder = null;
- if (mOnUnbind != null) {
- mOnUnbind.run();
- }
- }
-
- @Override
- public final void onBindingDied(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
-
- onBestServiceChanged(true);
- }
-
- @Override
- public final void onNullBinding(ComponentName component) {
- Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
- }
-
- void onUserSwitched(@UserIdInt int userId) {
- mCurrentUserId = userId;
- onBestServiceChanged(false);
- }
-
- void onUserUnlocked(@UserIdInt int userId) {
- if (userId == mCurrentUserId) {
- onBestServiceChanged(false);
- }
- }
-
/**
- * Runs the given function asynchronously if and only if currently connected. Suppresses any
- * RemoteException thrown during execution.
+ * Creates a new ServiceWatcher instance.
*/
- public final void runOnBinder(BinderRunner runner) {
- mHandler.post(() -> {
- if (mBinder == null) {
- runner.onError();
- return;
- }
-
- try {
- runner.run(mBinder);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, getLogPrefix() + " exception running on " + mTargetService, e);
- runner.onError();
- }
- });
- }
-
- private String getLogPrefix() {
- return "[" + mIntent.getAction() + "]";
- }
-
- @Override
- public String toString() {
- return mTargetService.toString();
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+ Context context,
+ String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return create(context, FgThread.getHandler(), tag, serviceSupplier, serviceListener);
}
/**
- * Dump for debugging.
+ * Creates a new ServiceWatcher instance that runs on the given handler.
*/
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("target service=" + mTargetService);
- pw.println("connected=" + (mBinder != null));
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+ Context context,
+ Handler handler,
+ String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return new ServiceWatcherImpl<>(context, handler, tag, serviceSupplier, serviceListener);
}
-}
+
+ /**
+ * Returns true if there is at least one service that the ServiceWatcher could hypothetically
+ * bind to, as selected by the {@link ServiceSupplier}.
+ */
+ boolean checkServiceResolves();
+
+ /**
+ * Registers the ServiceWatcher, so that it will begin maintaining an active binding to the
+ * service selected by {@link ServiceSupplier}, until {@link #unregister()} is called.
+ */
+ void register();
+
+ /**
+ * Unregisters the ServiceWatcher, so that it will release any active bindings. If the
+ * ServiceWatcher is currently bound, this will result in one final
+ * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
+ * (but which is guaranteed to occur before any further
+ * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
+ * call to {@link #register()}).
+ */
+ void unregister();
+
+ /**
+ * Runs the given binder operation on the currently bound service (if available). The operation
+ * will always fail if the ServiceWatcher is not currently registered.
+ */
+ void runOnBinder(BinderOperation operation);
+
+ /**
+ * Dumps ServiceWatcher information.
+ */
+ void dump(PrintWriter pw);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
new file mode 100644
index 0000000..7757a7a
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -0,0 +1,297 @@
+/*
+ * 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.servicewatcher;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.content.Context.BIND_NOT_VISIBLE;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.Preconditions;
+import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows
+ * us to store the generic relationship between the service supplier and the service listener, while
+ * hiding the generics from clients, simplifying the API.
+ */
+class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher,
+ ServiceChangedListener {
+
+ static final String TAG = "ServiceWatcher";
+ static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final long RETRY_DELAY_MS = 15 * 1000;
+
+ final Context mContext;
+ final Handler mHandler;
+ final String mTag;
+ final ServiceSupplier<TBoundServiceInfo> mServiceSupplier;
+ final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
+
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ return true;
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ onServiceChanged(false);
+ }
+ };
+
+ @GuardedBy("this")
+ private boolean mRegistered = false;
+ @GuardedBy("this")
+ private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
+
+ ServiceWatcherImpl(Context context, Handler handler, String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ mContext = context;
+ mHandler = handler;
+ mTag = tag;
+ mServiceSupplier = serviceSupplier;
+ mServiceListener = serviceListener;
+ }
+
+ @Override
+ public boolean checkServiceResolves() {
+ return mServiceSupplier.hasMatchingService();
+ }
+
+ @Override
+ public synchronized void register() {
+ Preconditions.checkState(!mRegistered);
+
+ mRegistered = true;
+ mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler);
+ mServiceSupplier.register(this);
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void unregister() {
+ Preconditions.checkState(mRegistered);
+
+ mServiceSupplier.unregister();
+ mPackageMonitor.unregister();
+ mRegistered = false;
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void onServiceChanged() {
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void runOnBinder(BinderOperation operation) {
+ MyServiceConnection serviceConnection = mServiceConnection;
+ mHandler.post(() -> serviceConnection.runOnBinder(operation));
+ }
+
+ synchronized void onServiceChanged(boolean forceRebind) {
+ TBoundServiceInfo newBoundServiceInfo;
+ if (mRegistered) {
+ newBoundServiceInfo = mServiceSupplier.getServiceInfo();
+ } else {
+ newBoundServiceInfo = null;
+ }
+
+ if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
+ newBoundServiceInfo)) {
+ MyServiceConnection oldServiceConnection = mServiceConnection;
+ MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
+ mServiceConnection = newServiceConnection;
+ mHandler.post(() -> {
+ oldServiceConnection.unbind();
+ newServiceConnection.bind();
+ });
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mServiceConnection.getBoundServiceInfo().toString();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("target service=" + mServiceConnection.getBoundServiceInfo());
+ pw.println("connected=" + mServiceConnection.isConnected());
+ }
+
+ // runs on the handler thread, and expects most of it's methods to be called from that thread
+ private class MyServiceConnection implements ServiceConnection {
+
+ private final @Nullable TBoundServiceInfo mBoundServiceInfo;
+
+ // volatile so that isConnected can be called from any thread easily
+ private volatile @Nullable IBinder mBinder;
+ private @Nullable Runnable mRebinder;
+
+ MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
+ mBoundServiceInfo = boundServiceInfo;
+ }
+
+ // may be called from any thread
+ @Nullable TBoundServiceInfo getBoundServiceInfo() {
+ return mBoundServiceInfo;
+ }
+
+ // may be called from any thread
+ boolean isConnected() {
+ return mBinder != null;
+ }
+
+ void bind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ Log.i(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
+
+ Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
+ mBoundServiceInfo.getComponentName());
+ if (!mContext.bindServiceAsUser(bindIntent, this,
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+ mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+ Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+ mRebinder = this::bind;
+ mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+ } else {
+ mRebinder = null;
+ }
+ }
+
+ void unbind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
+ }
+
+ if (mRebinder != null) {
+ mHandler.removeCallbacks(mRebinder);
+ mRebinder = null;
+ } else {
+ mContext.unbindService(this);
+ }
+
+ onServiceDisconnected(mBoundServiceInfo.getComponentName());
+ }
+
+ void runOnBinder(BinderOperation operation) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ operation.onError();
+ return;
+ }
+
+ try {
+ operation.run(mBinder);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ operation.onError();
+ }
+ }
+
+ @Override
+ public final void onServiceConnected(ComponentName component, IBinder binder) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+ Preconditions.checkState(mBinder == null);
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] connected to " + component.toShortString());
+ }
+
+ mBinder = binder;
+
+ if (mServiceListener != null) {
+ try {
+ mServiceListener.onBind(binder, mBoundServiceInfo);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ }
+ }
+ }
+
+ @Override
+ public final void onServiceDisconnected(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
+ }
+
+ mBinder = null;
+ if (mServiceListener != null) {
+ mServiceListener.onUnbind();
+ }
+ }
+
+ @Override
+ public final void onBindingDied(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ Log.i(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
+
+ onServiceChanged(true);
+ }
+
+ @Override
+ public final void onNullBinding(ComponentName component) {
+ Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index dedb3ac..ff6c2f5 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -27,11 +27,13 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.os.IVold;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
@@ -53,6 +55,7 @@
private final Object mLock = new Object();
private final Context mContext;
+ private final UserManager mUserManager;
@GuardedBy("mLock")
private final SparseArray<StorageUserConnection> mConnections = new SparseArray<>();
@@ -63,6 +66,30 @@
public StorageSessionController(Context context) {
mContext = Objects.requireNonNull(context);
+ mUserManager = mContext.getSystemService(UserManager.class);
+ }
+
+ /**
+ * Returns userId for the volume to be used in the StorageUserConnection.
+ * If the user is a clone profile, it will use the same connection
+ * as the parent user, and hence this method returns the parent's userId. Else, it returns the
+ * volume's mountUserId
+ * @param vol for which the storage session has to be started
+ * @return userId for connection for this volume
+ */
+ public int getConnectionUserIdForVolume(VolumeInfo vol) {
+ final Context volumeUserContext = mContext.createContextAsUser(
+ UserHandle.of(vol.mountUserId), 0);
+ boolean sharesMediaWithParent = volumeUserContext.getSystemService(
+ UserManager.class).sharesMediaWithParent();
+
+ UserInfo userInfo = mUserManager.getUserInfo(vol.mountUserId);
+ if (userInfo != null && sharesMediaWithParent) {
+ // Clones use the same connection as their parent
+ return userInfo.profileGroupId;
+ } else {
+ return vol.mountUserId;
+ }
}
/**
@@ -88,7 +115,7 @@
Slog.i(TAG, "On volume mount " + vol);
String sessionId = vol.getId();
- int userId = vol.getMountUserId();
+ int userId = getConnectionUserIdForVolume(vol);
StorageUserConnection connection = null;
synchronized (mLock) {
@@ -120,7 +147,7 @@
return;
}
String sessionId = vol.getId();
- int userId = vol.getMountUserId();
+ int userId = getConnectionUserIdForVolume(vol);
StorageUserConnection connection = null;
synchronized (mLock) {
@@ -191,7 +218,7 @@
Slog.i(TAG, "On volume remove " + vol);
String sessionId = vol.getId();
- int userId = vol.getMountUserId();
+ int userId = getConnectionUserIdForVolume(vol);
synchronized (mLock) {
StorageUserConnection connection = mConnections.get(userId);
diff --git a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
index a7767a4..23e3eba 100644
--- a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
+++ b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
@@ -16,8 +16,6 @@
package com.android.server.timezone;
-import com.android.server.LocalServices;
-
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -26,6 +24,8 @@
import android.content.Context;
import android.util.Slog;
+import com.android.server.LocalServices;
+
/**
* A JobService used to trigger time zone rules update work when a device falls idle.
*/
@@ -55,7 +55,7 @@
@Override
public boolean onStopJob(JobParameters params) {
// Reschedule if stopped unless it was cancelled due to unschedule().
- boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+ boolean reschedule = params.getStopReason() != JobParameters.STOP_REASON_CANCELLED_BY_APP;
Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
return reschedule;
}
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 2452c8d..222e852 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.net.ConnectivityManager;
import android.os.SystemProperties;
import android.util.ArraySet;
@@ -124,9 +124,7 @@
* device.
*/
public boolean isTelephonyTimeZoneDetectionFeatureSupported() {
- // TODO b/150583524 Avoid the use of a deprecated API.
- return mContext.getSystemService(ConnectivityManager.class)
- .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
}
/**
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index 4fa920e..f054c57 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -47,7 +47,7 @@
@NonNull String providerName,
@NonNull LocationTimeZoneProviderProxy proxy) {
super(providerMetricsLogger, threadingDomain, providerName,
- new ZoneInfoDbTimeZoneIdValidator());
+ new ZoneInfoDbTimeZoneProviderEventPreProcessor());
mProxy = Objects.requireNonNull(proxy);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index cc815dc6..e116a87 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -20,7 +20,6 @@
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
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;
@@ -86,18 +85,6 @@
}
/**
- * Used by {@link LocationTimeZoneProvider} to check if time zone IDs are understood
- * by the platform.
- */
- interface TimeZoneIdValidator {
-
- /**
- * Returns whether {@code timeZoneId} is supported by the platform or not.
- */
- boolean isValid(@NonNull String timeZoneId);
- }
-
- /**
* Listener interface used to log provider events for metrics.
*/
interface ProviderMetricsLogger {
@@ -386,19 +373,20 @@
// Non-null and effectively final after initialize() is called.
ProviderListener mProviderListener;
- @NonNull private TimeZoneIdValidator mTimeZoneIdValidator;
+ @NonNull private final TimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
/** Creates the instance. */
LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
- @NonNull TimeZoneIdValidator timeZoneIdValidator) {
+ @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger);
mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
mSharedLock = threadingDomain.getLockObject();
mProviderName = Objects.requireNonNull(providerName);
- mTimeZoneIdValidator = Objects.requireNonNull(timeZoneIdValidator);
+ mTimeZoneProviderEventPreProcessor =
+ Objects.requireNonNull(timeZoneProviderEventPreProcessor);
}
/**
@@ -639,24 +627,8 @@
mThreadingDomain.assertCurrentThread();
Objects.requireNonNull(timeZoneProviderEvent);
- // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
- // the device's time zone. This logic prevents bad time zone IDs entering the time zone
- // detection logic from third party code.
- //
- // An event containing an unknown time zone ID could occur if the provider is using a
- // different TZDB version than the device. Provider developers are expected to take steps to
- // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
- // rules, or providing IDs based on the device's TZDB version, so this is not considered a
- // common case.
- //
- // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
- // enables immediate failover to a secondary provider, one that might provide valid IDs for
- // the same location, which should provide better behavior than just ignoring the event.
- if (hasInvalidTimeZones(timeZoneProviderEvent)) {
- infoLog("event=" + timeZoneProviderEvent + " has unsupported time zones. "
- + "Replacing it with uncertain event.");
- timeZoneProviderEvent = TimeZoneProviderEvent.createUncertainEvent();
- }
+ timeZoneProviderEvent =
+ mTimeZoneProviderEventPreProcessor.preProcess(timeZoneProviderEvent);
synchronized (mSharedLock) {
debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName
@@ -755,20 +727,6 @@
}
}
- private boolean hasInvalidTimeZones(@NonNull TimeZoneProviderEvent event) {
- if (event.getSuggestion() == null) {
- return false;
- }
-
- for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
- if (!mTimeZoneIdValidator.isValid(timeZone)) {
- return true;
- }
- }
-
- return false;
- }
-
@GuardedBy("mSharedLock")
private void assertIsStarted() {
ProviderState currentState = mCurrentState.get();
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
index 0b51488..6c3f016 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
@@ -16,19 +16,14 @@
package com.android.server.timezonedetector.location;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
+import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
-
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -39,11 +34,12 @@
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
-import java.util.function.Predicate;
/**
* System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the
@@ -52,7 +48,8 @@
* different process. As "remote" providers are bound / unbound this proxy will rebind to the "best"
* available remote process.
*/
-class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
+class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy implements
+ ServiceListener<BoundServiceInfo> {
@NonNull private final ServiceWatcher mServiceWatcher;
@@ -69,38 +66,13 @@
super(context, threadingDomain);
mManagerProxy = null;
mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
-
- // A predicate that is used to confirm that an intent service can be used as a
- // location-based TimeZoneProvider. The service must:
- // 1) Declare android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE" - this
- // ensures that the provider will only communicate with the system server.
- // 2) Be in an application that has been granted the
- // android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE permission. This
- // ensures only trusted time zone providers will be discovered.
- final String requiredClientPermission = Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
- final String requiredPermission =
- Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
- Predicate<ResolveInfo> intentServiceCheckPredicate = resolveInfo -> {
- ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-
- boolean hasClientPermissionRequirement =
- requiredClientPermission.equals(serviceInfo.permission);
-
- String packageName = serviceInfo.packageName;
- PackageManager packageManager = context.getPackageManager();
- int checkResult = packageManager.checkPermission(requiredPermission, packageName);
- boolean hasRequiredPermission = checkResult == PERMISSION_GRANTED;
-
- boolean result = hasClientPermissionRequirement && hasRequiredPermission;
- if (!result) {
- warnLog("resolveInfo=" + resolveInfo + " does not meet requirements:"
- + " hasClientPermissionRequirement=" + hasClientPermissionRequirement
- + ", hasRequiredPermission=" + hasRequiredPermission);
- }
- return result;
- };
- mServiceWatcher = new ServiceWatcher(context, handler, action, this::onBind, this::onUnbind,
- enableOverlayResId, nonOverlayPackageResId, intentServiceCheckPredicate);
+ mServiceWatcher = ServiceWatcher.create(context,
+ handler,
+ "RealLocationTimeZoneProviderProxy",
+ new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+ nonOverlayPackageResId, BIND_TIME_ZONE_PROVIDER_SERVICE,
+ INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE),
+ this);
}
@Override
@@ -123,7 +95,8 @@
return resolves;
}
- private void onBind(IBinder binder, BoundService boundService) {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundService) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -138,7 +111,8 @@
}
}
- private void onUnbind() {
+ @Override
+ public void onUnbind() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -199,7 +173,7 @@
synchronized (mSharedLock) {
ipw.println("{RealLocationTimeZoneProviderProxy}");
ipw.println("mRequest=" + mRequest);
- mServiceWatcher.dump(null, ipw, args);
+ mServiceWatcher.dump(ipw);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
similarity index 64%
rename from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
rename to services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
index cab5ad2..951e9d0 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
@@ -18,13 +18,16 @@
import android.annotation.NonNull;
-import com.android.i18n.timezone.ZoneInfoDb;
+/**
+ * Used by {@link LocationTimeZoneProvider} to ensure that all time zone IDs are understood by the
+ * platform.
+ */
+public interface TimeZoneProviderEventPreProcessor {
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
+ /**
+ * May return uncertain event if {@code timeZoneProviderEvent} is ill-formed or drop/rewrite
+ * time zone IDs.
+ */
+ TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent timeZoneProviderEvent);
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..0f4367d
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -0,0 +1,72 @@
+/*
+ * 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.timezonedetector.location;
+
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
+
+import android.annotation.NonNull;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+/**
+ * {@link TimeZoneProviderEventPreProcessor} implementation which makes validations against
+ * {@link ZoneInfoDb}.
+ */
+public class ZoneInfoDbTimeZoneProviderEventPreProcessor
+ implements TimeZoneProviderEventPreProcessor {
+
+ /**
+ * Returns uncertain event if {@code event} has at least one unsupported time zone ID.
+ */
+ @Override
+ public TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent event) {
+ if (event.getSuggestion() == null || event.getSuggestion().getTimeZoneIds().isEmpty()) {
+ return event;
+ }
+
+ // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
+ // the device's time zone. This logic prevents bad time zone IDs entering the time zone
+ // detection logic from third party code.
+ //
+ // An event containing an unknown time zone ID could occur if the provider is using a
+ // different TZDB version than the device. Provider developers are expected to take steps to
+ // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
+ // rules, or providing IDs based on the device's TZDB version, so this is not considered a
+ // common case.
+ //
+ // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
+ // enables immediate failover to a secondary provider, one that might provide valid IDs for
+ // the same location, which should provide better behavior than just ignoring the event.
+ if (hasInvalidZones(event)) {
+ return TimeZoneProviderEvent.createUncertainEvent();
+ }
+
+ return event;
+ }
+
+ private static boolean hasInvalidZones(TimeZoneProviderEvent event) {
+ for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
+ if (!ZoneInfoDb.getInstance().hasTimeZone(timeZone)) {
+ infoLog("event=" + event + " has unsupported zone(" + timeZone + ")");
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e84ee672..1e897ea 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,17 +16,23 @@
package com.android.server.vibrator;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibrationEffect;
import android.os.IBinder;
import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
/** Represents a vibration request to the vibrator service. */
final class Vibration {
@@ -61,6 +67,7 @@
public final String opPkg;
public final String reason;
public final IBinder token;
+ public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
/** The actual effect to be played. */
@Nullable
@@ -113,17 +120,70 @@
}
/**
- * Replace this vibration effect if given {@code scaledEffect} is different, preserving the
- * original one for debug purposes.
+ * Return the effect to be played when given prebaked effect id is not supported by the
+ * vibrator.
*/
- public void updateEffect(@NonNull CombinedVibrationEffect newEffect) {
- if (newEffect.equals(mEffect)) {
- return;
+ @Nullable
+ public VibrationEffect getFallback(int effectId) {
+ return mFallbacks.get(effectId);
+ }
+
+ /**
+ * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
+ * which might be necessary for replacement in realtime.
+ */
+ public void addFallback(int effectId, VibrationEffect effect) {
+ mFallbacks.put(effectId, effect);
+ }
+
+ /**
+ * Applied update function to the current effect held by this vibration, and to each fallback
+ * effect added.
+ */
+ public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
+ CombinedVibrationEffect newEffect = transformCombinedEffect(mEffect, updateFn);
+ if (!newEffect.equals(mEffect)) {
+ if (mOriginalEffect == null) {
+ mOriginalEffect = mEffect;
+ }
+ mEffect = newEffect;
}
- if (mOriginalEffect == null) {
- mOriginalEffect = mEffect;
+ for (int i = 0; i < mFallbacks.size(); i++) {
+ mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
}
- mEffect = newEffect;
+ }
+
+ /**
+ * Creates a new {@link CombinedVibrationEffect} by applying the given transformation function
+ * to each {@link VibrationEffect}.
+ */
+ private static CombinedVibrationEffect transformCombinedEffect(
+ CombinedVibrationEffect combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
+ if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
+ VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
+ return CombinedVibrationEffect.createSynced(fn.apply(effect));
+ } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects();
+ CombinedVibrationEffect.SyncedCombination combination =
+ CombinedVibrationEffect.startSynced();
+ for (int i = 0; i < effects.size(); i++) {
+ combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
+ }
+ return combination.combine();
+ } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
+ List<CombinedVibrationEffect> effects =
+ ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects();
+ CombinedVibrationEffect.SequentialCombination combination =
+ CombinedVibrationEffect.startSequential();
+ for (CombinedVibrationEffect effect : effects) {
+ combination.addNext(transformCombinedEffect(effect, fn));
+ }
+ return combination.combine();
+ } else {
+ // Unknown combination, return same effect.
+ return combinedEffect;
+ }
}
/** Return true is current status is different from {@link Status#RUNNING}. */
@@ -272,57 +332,49 @@
private void dumpEffect(
ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
final long token = proto.start(fieldId);
- if (effect instanceof VibrationEffect.OneShot) {
- dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
- } else if (effect instanceof VibrationEffect.Waveform) {
- dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect);
- } else if (effect instanceof VibrationEffect.Prebaked) {
- dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect);
- } else if (effect instanceof VibrationEffect.Composed) {
- dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect);
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ for (VibrationEffectSegment segment : composed.getSegments()) {
+ dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
}
+ proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
proto.end(token);
}
private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.OneShot effect) {
+ VibrationEffectSegment segment) {
final long token = proto.start(fieldId);
- proto.write(OneShotProto.DURATION, (int) effect.getDuration());
- proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude());
+ if (segment instanceof StepSegment) {
+ dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment);
+ } else if (segment instanceof PrebakedSegment) {
+ dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment);
+ } else if (segment instanceof PrimitiveSegment) {
+ dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment);
+ }
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) {
+ final long token = proto.start(fieldId);
+ proto.write(StepSegmentProto.DURATION, segment.getDuration());
+ proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude());
proto.end(token);
}
private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.Waveform effect) {
+ PrebakedSegment segment) {
final long token = proto.start(fieldId);
- for (long timing : effect.getTimings()) {
- proto.write(WaveformProto.TIMINGS, (int) timing);
- }
- for (int amplitude : effect.getAmplitudes()) {
- proto.write(WaveformProto.AMPLITUDES, amplitude);
- }
- proto.write(WaveformProto.REPEAT, effect.getRepeatIndex() >= 0);
+ proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId());
+ proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength());
+ proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback());
proto.end(token);
}
private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.Prebaked effect) {
+ PrimitiveSegment segment) {
final long token = proto.start(fieldId);
- proto.write(PrebakedProto.EFFECT_ID, effect.getId());
- proto.write(PrebakedProto.EFFECT_STRENGTH, effect.getEffectStrength());
- proto.write(PrebakedProto.FALLBACK, effect.shouldFallback());
- proto.end(token);
- }
-
- private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.Composed effect) {
- final long token = proto.start(fieldId);
- for (VibrationEffect.Composition.PrimitiveEffect primitive :
- effect.getPrimitiveEffects()) {
- proto.write(ComposedProto.EFFECT_IDS, primitive.id);
- proto.write(ComposedProto.EFFECT_SCALES, primitive.scale);
- proto.write(ComposedProto.DELAYS, primitive.delay);
- }
+ proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId());
+ proto.write(PrimitiveSegmentProto.SCALE, segment.getScale());
+ proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
proto.end(token);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 10393f6..f481772 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -18,16 +18,13 @@
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
-import android.os.CombinedVibrationEffect;
import android.os.IExternalVibratorService;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.vibrator.PrebakedSegment;
import android.util.Slog;
import android.util.SparseArray;
-import java.util.List;
-import java.util.Objects;
-
/** Controls vibration scaling. */
final class VibrationScaler {
private static final String TAG = "VibrationScaler";
@@ -90,43 +87,6 @@
}
/**
- * Scale a {@link CombinedVibrationEffect} based on the given usage hint for this vibration.
- *
- * @param combinedEffect the effect to be scaled
- * @param usageHint one of VibrationAttributes.USAGE_*
- * @return The same given effect, if no changes were made, or a new
- * {@link CombinedVibrationEffect} with resolved and scaled amplitude
- */
- public <T extends CombinedVibrationEffect> T scale(CombinedVibrationEffect combinedEffect,
- int usageHint) {
- if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
- VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
- return (T) CombinedVibrationEffect.createSynced(scale(effect, usageHint));
- } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
- SparseArray<VibrationEffect> effects =
- ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects();
- CombinedVibrationEffect.SyncedCombination combination =
- CombinedVibrationEffect.startSynced();
- for (int i = 0; i < effects.size(); i++) {
- combination.addVibrator(effects.keyAt(i), scale(effects.valueAt(i), usageHint));
- }
- return (T) combination.combine();
- } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
- List<CombinedVibrationEffect> effects =
- ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects();
- CombinedVibrationEffect.SequentialCombination combination =
- CombinedVibrationEffect.startSequential();
- for (CombinedVibrationEffect effect : effects) {
- combination.addNext(scale(effect, usageHint));
- }
- return (T) combination.combine();
- } else {
- // Unknown combination, return same effect.
- return (T) combinedEffect;
- }
- }
-
- /**
* Scale a {@link VibrationEffect} based on the given usage hint for this vibration.
*
* @param effect the effect to be scaled
@@ -135,33 +95,10 @@
* resolved and scaled amplitude
*/
public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) {
- if (effect instanceof VibrationEffect.Prebaked) {
- // Prebaked effects are always just a direct translation to EffectStrength.
- int intensity = mSettingsController.getCurrentIntensity(usageHint);
- int newStrength = intensityToEffectStrength(intensity);
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- int strength = prebaked.getEffectStrength();
- VibrationEffect fallback = prebaked.getFallbackEffect();
-
- if (fallback != null) {
- VibrationEffect scaledFallback = scale(fallback, usageHint);
- if (strength == newStrength && Objects.equals(fallback, scaledFallback)) {
- return (T) prebaked;
- }
-
- return (T) new VibrationEffect.Prebaked(prebaked.getId(), newStrength,
- scaledFallback);
- } else if (strength == newStrength) {
- return (T) prebaked;
- } else {
- return (T) new VibrationEffect.Prebaked(prebaked.getId(), prebaked.shouldFallback(),
- newStrength);
- }
- }
-
- effect = effect.resolve(mDefaultVibrationAmplitude);
int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
+ int newEffectStrength = intensityToEffectStrength(currentIntensity);
+ effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude);
ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity);
if (scale == null) {
@@ -171,7 +108,21 @@
return (T) effect;
}
- return effect.scale(scale.factor);
+ return (T) effect.scale(scale.factor);
+ }
+
+ /**
+ * Scale a {@link PrebakedSegment} based on the given usage hint for this vibration.
+ *
+ * @param prebaked the prebaked segment to be scaled
+ * @param usageHint one of VibrationAttributes.USAGE_*
+ * @return The same segment if no changes were made, or a new {@link PrebakedSegment} with
+ * updated effect strength
+ */
+ public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) {
+ int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
+ int newEffectStrength = intensityToEffectStrength(currentIntensity);
+ return prebaked.applyEffectStrength(newEffectStrength);
}
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index b90408f..18063de 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -28,6 +28,10 @@
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.WorkSource;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import android.util.SparseArray;
@@ -248,22 +252,26 @@
* Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
* startIndex} until the next time it's vibrating amplitude is zero.
*/
- private static long getVibratorOnDuration(VibrationEffect.Waveform waveform, int startIndex) {
- long[] timings = waveform.getTimings();
- int[] amplitudes = waveform.getAmplitudes();
- int repeatIndex = waveform.getRepeatIndex();
+ private static long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
+ List<VibrationEffectSegment> segments = effect.getSegments();
+ int segmentCount = segments.size();
+ int repeatIndex = effect.getRepeatIndex();
int i = startIndex;
long timing = 0;
- while (timings[i] == 0 || amplitudes[i] != 0) {
- timing += timings[i++];
- if (i >= timings.length) {
- if (repeatIndex >= 0) {
- i = repeatIndex;
- // prevent infinite loop
- repeatIndex = -1;
- } else {
- break;
- }
+ while (i < segmentCount) {
+ if (!(segments.get(i) instanceof StepSegment)) {
+ break;
+ }
+ StepSegment stepSegment = (StepSegment) segments.get(i);
+ if (stepSegment.getAmplitude() == 0) {
+ break;
+ }
+ timing += stepSegment.getDuration();
+ i++;
+ if (i == segmentCount && repeatIndex >= 0) {
+ i = repeatIndex;
+ // prevent infinite loop
+ repeatIndex = -1;
}
if (i == startIndex) {
return 1000;
@@ -620,22 +628,14 @@
}
private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ VibrationEffectSegment firstSegment = composed.getSegments().get(0);
final long duration;
final long now = SystemClock.uptimeMillis();
- if (effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- duration = oneShot.getDuration();
- // Do NOT set amplitude here. This might be called between prepareSynced and
- // triggerSynced, so the vibrator is not actually turned on here.
- // The next steps will handle the amplitude after the vibrator has turned on.
- controller.on(duration, mVibration.id);
- nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot,
- now + duration + CALLBACKS_EXTRA_TIMEOUT));
- } else if (effect instanceof VibrationEffect.Waveform) {
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ if (firstSegment instanceof StepSegment) {
// Return the full duration of this waveform effect.
- duration = waveform.getDuration();
- long onDuration = getVibratorOnDuration(waveform, 0);
+ duration = effect.getDuration();
+ long onDuration = getVibratorOnDuration(composed, 0);
if (onDuration > 0) {
// Do NOT set amplitude here. This might be called between prepareSynced and
// triggerSynced, so the vibrator is not actually turned on here.
@@ -643,19 +643,31 @@
controller.on(onDuration, mVibration.id);
}
long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now;
- nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime));
- } else if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ nextSteps.add(new VibratorAmplitudeStep(now, controller, composed, offTime));
+ } else if (firstSegment instanceof PrebakedSegment) {
+ PrebakedSegment prebaked = (PrebakedSegment) firstSegment;
+ VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId());
duration = controller.on(prebaked, mVibration.id);
if (duration > 0) {
nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
controller));
- } else if (prebaked.getFallbackEffect() != null) {
- return startVibrating(prebaked.getFallbackEffect(), nextSteps);
+ } else if (prebaked.shouldFallback() && fallback != null) {
+ return startVibrating(fallback, nextSteps);
}
- } else if (effect instanceof VibrationEffect.Composed) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
- duration = controller.on(composed, mVibration.id);
+ } else if (firstSegment instanceof PrimitiveSegment) {
+ int segmentCount = composed.getSegments().size();
+ PrimitiveSegment[] primitives = new PrimitiveSegment[segmentCount];
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if (segment instanceof PrimitiveSegment) {
+ primitives[i] = (PrimitiveSegment) segment;
+ } else {
+ primitives[i] = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_NOOP,
+ /* scale= */ 1, /* delay= */ 0);
+ }
+ }
+ duration = controller.on(primitives, mVibration.id);
if (duration > 0) {
nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
controller));
@@ -713,33 +725,22 @@
/** Represents a step to change the amplitude of the vibrator. */
private final class VibratorAmplitudeStep extends Step {
public final VibratorController controller;
- public final VibrationEffect.Waveform waveform;
+ public final VibrationEffect.Composed effect;
public final int currentIndex;
- public final long expectedVibratorStopTime;
private long mNextVibratorStopTime;
VibratorAmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) {
- this(startTime, controller,
- (VibrationEffect.Waveform) VibrationEffect.createWaveform(
- new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()},
- /* repeat= */ -1),
- expectedVibratorStopTime);
+ VibrationEffect.Composed effect, long expectedVibratorStopTime) {
+ this(startTime, controller, effect, /* index= */ 0, expectedVibratorStopTime);
}
VibratorAmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.Waveform waveform, long expectedVibratorStopTime) {
- this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime);
- }
-
- VibratorAmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) {
+ VibrationEffect.Composed effect, int index, long expectedVibratorStopTime) {
super(startTime);
this.controller = controller;
- this.waveform = waveform;
+ this.effect = effect;
this.currentIndex = index;
- this.expectedVibratorStopTime = expectedVibratorStopTime;
mNextVibratorStopTime = expectedVibratorStopTime;
}
@@ -759,11 +760,16 @@
long latency = SystemClock.uptimeMillis() - startTime;
Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
}
- if (waveform.getTimings()[currentIndex] == 0) {
+ VibrationEffectSegment segment = effect.getSegments().get(currentIndex);
+ if (!(segment instanceof StepSegment)) {
+ return nextSteps();
+ }
+ StepSegment stepSegment = (StepSegment) segment;
+ if (stepSegment.getDuration() == 0) {
// Skip waveform entries with zero timing.
return nextSteps();
}
- int amplitude = waveform.getAmplitudes()[currentIndex];
+ float amplitude = stepSegment.getAmplitude();
if (amplitude == 0) {
stopVibrating();
return nextSteps();
@@ -771,7 +777,7 @@
if (startTime >= mNextVibratorStopTime) {
// Vibrator has stopped. Turn vibrator back on for the duration of another
// cycle before setting the amplitude.
- long onDuration = getVibratorOnDuration(waveform, currentIndex);
+ long onDuration = getVibratorOnDuration(effect, currentIndex);
if (onDuration > 0) {
startVibrating(onDuration);
mNextVibratorStopTime =
@@ -806,7 +812,7 @@
controller.on(duration, mVibration.id);
}
- private void changeAmplitude(int amplitude) {
+ private void changeAmplitude(float amplitude) {
if (DEBUG) {
Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
+ " to " + amplitude);
@@ -816,16 +822,16 @@
@NonNull
private List<Step> nextSteps() {
- long nextStartTime = startTime + waveform.getTimings()[currentIndex];
+ long nextStartTime = startTime + effect.getSegments().get(currentIndex).getDuration();
int nextIndex = currentIndex + 1;
- if (nextIndex >= waveform.getTimings().length) {
- nextIndex = waveform.getRepeatIndex();
+ if (nextIndex >= effect.getSegments().size()) {
+ nextIndex = effect.getRepeatIndex();
}
- if (nextIndex < 0) {
- return Arrays.asList(new VibratorOffStep(nextStartTime, controller));
- }
- return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform,
- nextIndex, mNextVibratorStopTime));
+ Step nextStep = nextIndex < 0
+ ? new VibratorOffStep(nextStartTime, controller)
+ : new VibratorAmplitudeStep(nextStartTime, controller, effect, nextIndex,
+ mNextVibratorStopTime);
+ return Arrays.asList(nextStep);
}
}
@@ -909,13 +915,13 @@
private long calculateRequiredSyncCapabilities(SparseArray<VibrationEffect> effects) {
long prepareCap = 0;
for (int i = 0; i < effects.size(); i++) {
- VibrationEffect effect = effects.valueAt(i);
- if (effect instanceof VibrationEffect.OneShot
- || effect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effects.valueAt(i);
+ VibrationEffectSegment firstSegment = composed.getSegments().get(0);
+ if (firstSegment instanceof StepSegment) {
prepareCap |= IVibratorManager.CAP_PREPARE_ON;
- } else if (effect instanceof VibrationEffect.Prebaked) {
+ } else if (firstSegment instanceof PrebakedSegment) {
prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
- } else if (effect instanceof VibrationEffect.Composed) {
+ } else if (firstSegment instanceof PrimitiveSegment) {
prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index e3dc70b..66200b3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -22,8 +22,9 @@
import android.os.IVibratorStateListener;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -156,21 +157,22 @@
* Update the predefined vibration effect saved with given id. This will remove the saved effect
* if given {@code effect} is {@code null}.
*/
- public void updateAlwaysOn(int id, @Nullable VibrationEffect.Prebaked effect) {
+ public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
return;
}
synchronized (mLock) {
- if (effect == null) {
+ if (prebaked == null) {
mNativeWrapper.alwaysOnDisable(id);
} else {
- mNativeWrapper.alwaysOnEnable(id, effect.getId(), effect.getEffectStrength());
+ mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(),
+ prebaked.getEffectStrength());
}
}
}
/** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
- public void setAmplitude(int amplitude) {
+ public void setAmplitude(float amplitude) {
synchronized (mLock) {
if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
mNativeWrapper.setAmplitude(amplitude);
@@ -199,10 +201,10 @@
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(VibrationEffect.Prebaked effect, long vibrationId) {
+ public long on(PrebakedSegment prebaked, long vibrationId) {
synchronized (mLock) {
- long duration = mNativeWrapper.perform(effect.getId(), effect.getEffectStrength(),
- vibrationId);
+ long duration = mNativeWrapper.perform(prebaked.getEffectId(),
+ prebaked.getEffectStrength(), vibrationId);
if (duration > 0) {
notifyVibratorOnLocked();
}
@@ -211,21 +213,18 @@
}
/**
- * Plays composited vibration effect, using {@code vibrationId} or completion callback to
- * {@link OnVibrationCompleteListener}.
+ * Plays a composition of vibration primitives, using {@code vibrationId} or completion callback
+ * to {@link OnVibrationCompleteListener}.
*
* <p>This will affect the state of {@link #isVibrating()}.
*
* @return The duration of the effect playing, or 0 if unsupported.
*/
- public long on(VibrationEffect.Composed effect, long vibrationId) {
+ public long on(PrimitiveSegment[] primitives, long vibrationId) {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
return 0;
}
synchronized (mLock) {
- VibrationEffect.Composition.PrimitiveEffect[] primitives =
- effect.getPrimitiveEffects().toArray(
- new VibrationEffect.Composition.PrimitiveEffect[0]);
long duration = mNativeWrapper.compose(primitives, vibrationId);
if (duration > 0) {
notifyVibratorOnLocked();
@@ -313,13 +312,13 @@
private static native boolean isAvailable(long nativePtr);
private static native void on(long nativePtr, long milliseconds, long vibrationId);
private static native void off(long nativePtr);
- private static native void setAmplitude(long nativePtr, int amplitude);
+ private static native void setAmplitude(long nativePtr, float amplitude);
private static native int[] getSupportedEffects(long nativePtr);
private static native int[] getSupportedPrimitives(long nativePtr);
- private static native long performEffect(
- long nativePtr, long effect, long strength, long vibrationId);
- private static native long performComposedEffect(long nativePtr,
- VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId);
+ private static native long performEffect(long nativePtr, long effect, long strength,
+ long vibrationId);
+ private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
+ long vibrationId);
private static native void setExternalControl(long nativePtr, boolean enabled);
private static native long getCapabilities(long nativePtr);
private static native void alwaysOnEnable(long nativePtr, long id, long effect,
@@ -359,7 +358,7 @@
}
/** Sets the amplitude for the vibrator to run. */
- public void setAmplitude(int amplitude) {
+ public void setAmplitude(float amplitude) {
setAmplitude(mNativePtr, amplitude);
}
@@ -379,9 +378,8 @@
}
/** Turns vibrator on to perform one of the supported composed effects. */
- public long compose(
- VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) {
- return performComposedEffect(mNativePtr, effect, vibrationId);
+ public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+ return performComposedEffect(mNativePtr, primitives, vibrationId);
}
/** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c9751bb..5fd1d7a 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -48,6 +48,8 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -312,7 +314,7 @@
}
attrs = fixupVibrationAttributes(attrs);
synchronized (mLock) {
- SparseArray<VibrationEffect.Prebaked> effects = fixupAlwaysOnEffectsLocked(effect);
+ SparseArray<PrebakedSegment> effects = fixupAlwaysOnEffectsLocked(effect);
if (effects == null) {
// Invalid effects set in CombinedVibrationEffect, or always-on capability is
// missing on individual vibrators.
@@ -347,8 +349,7 @@
attrs = fixupVibrationAttributes(attrs);
Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
uid, opPkg, reason);
- // Update with fixed up effect to keep the original effect in Vibration for debugging.
- vib.updateEffect(fixupVibrationEffect(effect));
+ fillVibrationFallbacks(vib, effect);
synchronized (mLock) {
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
@@ -476,7 +477,7 @@
private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
for (int i = 0; i < vib.effects.size(); i++) {
VibratorController vibrator = mVibrators.get(vib.effects.keyAt(i));
- VibrationEffect.Prebaked effect = vib.effects.valueAt(i);
+ PrebakedSegment effect = vib.effects.valueAt(i);
if (vibrator == null) {
continue;
}
@@ -496,7 +497,7 @@
private Vibration.Status startVibrationLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
+ vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage()));
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
if (inputDevicesAvailable) {
@@ -757,43 +758,38 @@
* Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
* VibrationSettings#getFallbackEffect}.
*/
- private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
+ private void fillVibrationFallbacks(Vibration vib, CombinedVibrationEffect effect) {
if (effect instanceof CombinedVibrationEffect.Mono) {
- return CombinedVibrationEffect.createSynced(
- fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
+ fillVibrationFallbacks(vib, ((CombinedVibrationEffect.Mono) effect).getEffect());
} else if (effect instanceof CombinedVibrationEffect.Stereo) {
- CombinedVibrationEffect.SyncedCombination combination =
- CombinedVibrationEffect.startSynced();
SparseArray<VibrationEffect> effects =
((CombinedVibrationEffect.Stereo) effect).getEffects();
for (int i = 0; i < effects.size(); i++) {
- combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
+ fillVibrationFallbacks(vib, effects.valueAt(i));
}
- return combination.combine();
} else if (effect instanceof CombinedVibrationEffect.Sequential) {
- CombinedVibrationEffect.SequentialCombination combination =
- CombinedVibrationEffect.startSequential();
List<CombinedVibrationEffect> effects =
((CombinedVibrationEffect.Sequential) effect).getEffects();
- for (CombinedVibrationEffect e : effects) {
- combination.addNext(fixupVibrationEffect(e));
+ for (int i = 0; i < effects.size(); i++) {
+ fillVibrationFallbacks(vib, effects.get(i));
}
- return combination.combine();
}
- return effect;
}
- private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Prebaked
- && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
- if (fallback != null) {
- return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
- fallback);
+ private void fillVibrationFallbacks(Vibration vib, VibrationEffect effect) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ int segmentCount = composed.getSegments().size();
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = composed.getSegments().get(i);
+ if (segment instanceof PrebakedSegment) {
+ PrebakedSegment prebaked = (PrebakedSegment) segment;
+ VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
+ prebaked.getEffectId());
+ if (prebaked.shouldFallback() && fallback != null) {
+ vib.addFallback(prebaked.getEffectId(), fallback);
+ }
}
}
- return effect;
}
/**
@@ -819,7 +815,7 @@
@GuardedBy("mLock")
@Nullable
- private SparseArray<VibrationEffect.Prebaked> fixupAlwaysOnEffectsLocked(
+ private SparseArray<PrebakedSegment> fixupAlwaysOnEffectsLocked(
CombinedVibrationEffect effect) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked");
try {
@@ -833,17 +829,17 @@
// Only synced combinations can be used for always-on effects.
return null;
}
- SparseArray<VibrationEffect.Prebaked> result = new SparseArray<>();
+ SparseArray<PrebakedSegment> result = new SparseArray<>();
for (int i = 0; i < effects.size(); i++) {
- VibrationEffect prebaked = effects.valueAt(i);
- if (!(prebaked instanceof VibrationEffect.Prebaked)) {
+ PrebakedSegment prebaked = extractPrebakedSegment(effects.valueAt(i));
+ if (prebaked == null) {
Slog.e(TAG, "Only prebaked effects supported for always-on.");
return null;
}
int vibratorId = effects.keyAt(i);
VibratorController vibrator = mVibrators.get(vibratorId);
if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
- result.put(vibratorId, (VibrationEffect.Prebaked) prebaked);
+ result.put(vibratorId, prebaked);
}
}
if (result.size() == 0) {
@@ -855,6 +851,20 @@
}
}
+ @Nullable
+ private static PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.Composed) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ if (composed.getSegments().size() == 1) {
+ VibrationEffectSegment segment = composed.getSegments().get(0);
+ if (segment instanceof PrebakedSegment) {
+ return (PrebakedSegment) segment;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
* allow bypassing {@link AppOpsManager} checks.
@@ -1008,10 +1018,10 @@
public final int uid;
public final String opPkg;
public final VibrationAttributes attrs;
- public final SparseArray<VibrationEffect.Prebaked> effects;
+ public final SparseArray<PrebakedSegment> effects;
AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs,
- SparseArray<VibrationEffect.Prebaked> effects) {
+ SparseArray<PrebakedSegment> effects) {
this.alwaysOnId = alwaysOnId;
this.uid = uid;
this.opPkg = opPkg;
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index e6d37b6..b947c88 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1511,10 +1511,15 @@
IBinder topFocusedWindowToken = null;
synchronized (mService.mGlobalLock) {
- // Do not send the windows if there is no top focus as
- // the window manager is still looking for where to put it.
- // We will do the work when we get a focus change callback.
- final WindowState topFocusedWindowState = getTopFocusWindow();
+ // If there is a recents animation running, then use the animation target as the
+ // top window state. Otherwise,do not send the windows if there is no top focus as
+ // the window manager is still looking for where to put it. We will do the work when
+ // we get a focus change callback.
+ final RecentsAnimationController controller =
+ mService.getRecentsAnimationController();
+ final WindowState topFocusedWindowState = controller != null
+ ? controller.getTargetAppMainWindow()
+ : getTopFocusWindow();
if (topFocusedWindowState == null) {
if (DEBUG) {
Slog.d(LOG_TAG, "top focused window is null, compute it again later");
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 3a0eb39..dc69175 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -287,6 +287,11 @@
if (mLastLaunchedActivity == r) {
return;
}
+ if (mLastLaunchedActivity != null) {
+ // Transfer the launch cookie because it is a consecutive launch event.
+ r.mLaunchCookie = mLastLaunchedActivity.mLaunchCookie;
+ mLastLaunchedActivity.mLaunchCookie = null;
+ }
mLastLaunchedActivity = r;
if (!r.noDisplay && !r.isReportedDrawn()) {
if (DEBUG_METRICS) Slog.i(TAG, "Add pending draw " + r);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7c23661..eadfbe2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6790,7 +6790,12 @@
// The app bounds hasn't been computed yet.
return false;
}
- final Configuration parentConfig = getParent().getConfiguration();
+ final WindowContainer parent = getParent();
+ if (parent == null) {
+ // The parent of detached Activity can be null.
+ return false;
+ }
+ final Configuration parentConfig = parent.getConfiguration();
// Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
// fields should be changed with density and bounds, so here only compares the most
// significant field.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 060323c..12c67bb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -607,4 +607,10 @@
*/
void commit() throws RemoteException;
}
+
+ /**
+ * A utility method to check AppOps and PackageManager for SYSTEM_ALERT_WINDOW permission.
+ */
+ public abstract boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+ String callingPackage);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 09f5c93..29c5cec 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -251,6 +251,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto;
import com.android.server.am.AppTimeTracker;
+import com.android.server.am.AssistDataRequester;
import com.android.server.am.BaseErrorDialog;
import com.android.server.am.PendingIntentController;
import com.android.server.am.PendingIntentRecord;
@@ -933,7 +934,8 @@
return getUserManager().hasUserRestriction(restriction, userId);
}
- boolean hasSystemAlertWindowPermission(int callingUid, int callingPid, String callingPackage) {
+ boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+ String callingPackage) {
final int mode = getAppOpsManager().noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
callingUid, callingPackage, /* featureId */ null, "");
if (mode == AppOpsManager.MODE_DEFAULT) {
@@ -2827,10 +2829,45 @@
@Override
public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
- Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
+ Bundle receiverExtras, IBinder activityToken, boolean checkActivityIsTop,
+ boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
- activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
- PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+ activityToken, checkActivityIsTop, newSessionId, UserHandle.getCallingUserId(),
+ null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+ }
+
+ @Override
+ public boolean requestAssistDataForTask(IAssistDataReceiver receiver, int taskId,
+ String callingPackageName) {
+ mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
+ "requestAssistDataForTask()");
+ final long callingId = Binder.clearCallingIdentity();
+ LocalService.ActivityTokens tokens = null;
+ try {
+ tokens = mInternal.getTopActivityForTask(taskId);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ if (tokens == null) {
+ Log.e(TAG, "Could not find activity for task " + taskId);
+ return false;
+ }
+
+ final AssistDataReceiverProxy proxy =
+ new AssistDataReceiverProxy(receiver, callingPackageName);
+ Object lock = new Object();
+ AssistDataRequester requester = new AssistDataRequester(mContext, mWindowManager,
+ getAppOpsManager(), proxy, lock, AppOpsManager.OP_ASSIST_STRUCTURE,
+ AppOpsManager.OP_NONE);
+
+ List<IBinder> topActivityToken = new ArrayList<>();
+ topActivityToken.add(tokens.getActivityToken());
+ requester.requestAssistData(topActivityToken, true /* fetchData */,
+ false /* fetchScreenshot */, true /* allowFetchData */,
+ false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */,
+ Binder.getCallingUid(), callingPackageName);
+
+ return true;
}
@Override
@@ -2844,7 +2881,7 @@
@Override
public Bundle getAssistContextExtras(int requestType) {
PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
- null, null, true /* focused */, true /* newSessionId */,
+ null, null, true /* checkActivityIsTop */, true /* newSessionId */,
UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
if (pae == null) {
return null;
@@ -3047,8 +3084,8 @@
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
- boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
- int flags) {
+ boolean checkActivityIsTop, boolean newSessionId, int userHandle, Bundle args,
+ long timeout, int flags) {
mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"enqueueAssistContext()");
@@ -3064,7 +3101,7 @@
Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity);
return null;
}
- if (focused) {
+ if (checkActivityIsTop) {
if (activityToken != null) {
ActivityRecord caller = ActivityRecord.forTokenLocked(activityToken);
if (activity != caller) {
@@ -6370,6 +6407,13 @@
return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
}
}
+
+ @Override
+ public boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+ String callingPackage) {
+ return ActivityTaskManagerService.this.hasSystemAlertWindowPermission(callingUid,
+ callingPid, callingPackage);
+ }
}
final class PackageConfigurationUpdaterImpl implements
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6d24105..6072a06 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -516,7 +516,7 @@
/** The delay to avoid toggling the animation quickly. */
private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
- private FixedRotationAnimationController mFixedRotationAnimationController;
+ private FadeRotationAnimationController mFadeRotationAnimationController;
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1590,8 +1590,8 @@
}
@VisibleForTesting
- @Nullable FixedRotationAnimationController getFixedRotationAnimationController() {
- return mFixedRotationAnimationController;
+ @Nullable FadeRotationAnimationController getFadeRotationAnimationController() {
+ return mFadeRotationAnimationController;
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1601,13 +1601,13 @@
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
if (mFixedRotationLaunchingApp == null && r != null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
- startFixedRotationAnimation(
+ startFadeRotationAnimation(
// Delay the hide animation to avoid blinking by clicking navigation bar that
// may toggle fixed rotation in a short time.
r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
- finishFixedRotationAnimationIfPossible();
+ finishFadeRotationAnimationIfPossible();
}
mFixedRotationLaunchingApp = r;
}
@@ -1714,12 +1714,12 @@
*
* @return {@code true} if the animation is executed right now.
*/
- private boolean startFixedRotationAnimation(boolean shouldDebounce) {
+ private boolean startFadeRotationAnimation(boolean shouldDebounce) {
if (shouldDebounce) {
mWmService.mH.postDelayed(() -> {
synchronized (mWmService.mGlobalLock) {
if (mFixedRotationLaunchingApp != null
- && startFixedRotationAnimation(false /* shouldDebounce */)) {
+ && startFadeRotationAnimation(false /* shouldDebounce */)) {
// Apply the transaction so the animation leash can take effect immediately.
getPendingTransaction().apply();
}
@@ -1727,23 +1727,41 @@
}, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
return false;
}
- if (mFixedRotationAnimationController == null) {
- mFixedRotationAnimationController = new FixedRotationAnimationController(this);
- mFixedRotationAnimationController.hide();
+ if (mFadeRotationAnimationController == null) {
+ mFadeRotationAnimationController = new FadeRotationAnimationController(this);
+ mFadeRotationAnimationController.hide();
return true;
}
return false;
}
/** Re-show the previously hidden windows if all seamless rotated windows are done. */
- void finishFixedRotationAnimationIfPossible() {
- final FixedRotationAnimationController controller = mFixedRotationAnimationController;
+ void finishFadeRotationAnimationIfPossible() {
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
controller.show();
- mFixedRotationAnimationController = null;
+ mFadeRotationAnimationController = null;
}
}
+ /** Shows the given window which may be hidden for screen frozen. */
+ void finishFadeRotationAnimation(WindowState w) {
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ if (controller != null && controller.show(w.mToken)) {
+ mFadeRotationAnimationController = null;
+ }
+ }
+
+ /** Returns {@code true} if the display should wait for the given window to stop freezing. */
+ boolean waitForUnfreeze(WindowState w) {
+ if (w.mForceSeamlesslyRotate) {
+ // The window should look no different before and after rotation.
+ return false;
+ }
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ return controller == null || !controller.isTargetToken(w.mToken);
+ }
+
void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
if (mFixedRotationLaunchingApp != null) {
// The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2964,6 +2982,13 @@
mScreenRotationAnimation.kill();
}
mScreenRotationAnimation = screenRotationAnimation;
+
+ // Hide the windows which are not significant in rotation animation. So that the windows
+ // don't need to block the unfreeze time.
+ if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()
+ && mFadeRotationAnimationController == null) {
+ startFadeRotationAnimation(false /* shouldDebounce */);
+ }
}
public ScreenRotationAnimation getRotationAnimation() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d0e4c40..6046cc6 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -626,7 +626,7 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimationIfPossible();
+ mDisplayContent.finishFadeRotationAnimationIfPossible();
}
private void prepareSeamlessRotation() {
@@ -717,7 +717,7 @@
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimationIfPossible();
+ mDisplayContent.finishFadeRotationAnimationIfPossible();
updateRotationAndSendNewConfigIfChanged();
}
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
new file mode 100644
index 0000000..5ee6928
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * Controller to fade out and in windows when the display is changing rotation. It can be used for
+ * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
+ * the windows until they are drawn with the new rotation.
+ */
+public class FadeRotationAnimationController extends FadeAnimationController {
+
+ private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
+ private final WindowManagerService mService;
+ /** If non-null, it usually indicates that there will be a screen rotation animation. */
+ private final Runnable mFrozenTimeoutRunnable;
+ private final WindowToken mNavBarToken;
+
+ public FadeRotationAnimationController(DisplayContent displayContent) {
+ super(displayContent);
+ mService = displayContent.mWmService;
+ mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
+ synchronized (mService.mGlobalLock) {
+ displayContent.finishFadeRotationAnimationIfPossible();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+ } : null;
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ final WindowState navigationBar = displayPolicy.getNavigationBar();
+ if (navigationBar != null) {
+ mNavBarToken = navigationBar.mToken;
+ final RecentsAnimationController controller = mService.getRecentsAnimationController();
+ final boolean navBarControlledByRecents =
+ controller != null && controller.isNavigationBarAttachedToApp();
+ // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
+ // bar is currently controlled by recents animation.
+ if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
+ mTargetWindowTokens.add(mNavBarToken);
+ }
+ } else {
+ mNavBarToken = null;
+ }
+ displayContent.forAllWindows(w -> {
+ if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
+ && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar) {
+ mTargetWindowTokens.add(w.mToken);
+ }
+ }, true /* traverseTopToBottom */);
+ }
+
+ /** Applies show animation on the previously hidden window tokens. */
+ void show() {
+ for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+ final WindowToken windowToken = mTargetWindowTokens.get(i);
+ fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+ }
+ mTargetWindowTokens.clear();
+ if (mFrozenTimeoutRunnable != null) {
+ mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ }
+ }
+
+ /**
+ * Returns {@code true} if all target windows are shown. It only takes effects if this
+ * controller is created for normal rotation.
+ */
+ boolean show(WindowToken token) {
+ if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
+ fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
+ if (mTargetWindowTokens.isEmpty()) {
+ mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
+ void hide() {
+ for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+ final WindowToken windowToken = mTargetWindowTokens.get(i);
+ fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+ }
+ if (mFrozenTimeoutRunnable != null) {
+ mService.mH.postDelayed(mFrozenTimeoutRunnable,
+ WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
+ }
+ }
+
+ /** Returns {@code true} if the window is handled by this controller. */
+ boolean isTargetToken(WindowToken token) {
+ return token == mNavBarToken || mTargetWindowTokens.contains(token);
+ }
+
+ @Override
+ public Animation getFadeInAnimation() {
+ if (mFrozenTimeoutRunnable != null) {
+ // Use a shorter animation so it is easier to align with screen rotation animation.
+ return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
+ }
+ return super.getFadeInAnimation();
+ }
+
+ @Override
+ public Animation getFadeOutAnimation() {
+ if (mFrozenTimeoutRunnable != null) {
+ // Hide the window immediately because screen should have been covered by screenshot.
+ return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
+ }
+ return super.getFadeOutAnimation();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
deleted file mode 100644
index aa73170..0000000
--- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
-
-import java.util.ArrayList;
-
-/**
- * Controller to fade out and in system ui when applying a fixed rotation transform to a window
- * token.
- *
- * The system bars will be fade out when the fixed rotation transform starts and will be fade in
- * once all surfaces have been rotated.
- */
-public class FixedRotationAnimationController extends FadeAnimationController {
-
- private final WindowState mStatusBar;
- private final WindowState mNavigationBar;
- private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2);
-
- public FixedRotationAnimationController(DisplayContent displayContent) {
- super(displayContent);
- final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
- mStatusBar = displayPolicy.getStatusBar();
-
- final RecentsAnimationController controller =
- displayContent.mWmService.getRecentsAnimationController();
- final boolean navBarControlledByRecents =
- controller != null && controller.isNavigationBarAttachedToApp();
- // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation bar
- // is currently controlled by recents animation.
- mNavigationBar = !displayPolicy.navigationBarCanMove()
- && !navBarControlledByRecents ? displayPolicy.getNavigationBar() : null;
- }
-
- /** Applies show animation on the previously hidden window tokens. */
- void show() {
- for (int i = mAnimatedWindowToken.size() - 1; i >= 0; i--) {
- final WindowToken windowToken = mAnimatedWindowToken.get(i);
- fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- }
-
- /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
- void hide() {
- if (mNavigationBar != null) {
- fadeWindowToken(false /* show */, mNavigationBar.mToken,
- ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- if (mStatusBar != null) {
- fadeWindowToken(false /* show */, mStatusBar.mToken,
- ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- }
-
- @Override
- public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
- super.fadeWindowToken(show, windowToken, animationType);
- mAnimatedWindowToken.add(windowToken);
- }
-}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 465042d..cda3fda 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -25,6 +25,7 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -458,10 +459,8 @@
InsetsPolicyAnimationControlCallbacks mControlCallbacks;
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
-
- super(show, false /* hasCallbacks */, types, false /* disable */,
- (int) (mDisplayContent.getDisplayMetrics().density * FLOATING_IME_BOTTOM_INSET
- + 0.5f));
+ super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+ false /* disable */, 0 /* floatingImeBottomInsets */);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
@@ -497,7 +496,7 @@
// the controlled types should be animated regardless of the frame.
mAnimationControl = new InsetsAnimationControlImpl(controls,
null /* frame */, state, mListener, typesReady, this,
- mListener.getDurationMs(), InsetsController.SYSTEM_BARS_INTERPOLATOR,
+ mListener.getDurationMs(), getInsetsInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, null /* translator */);
SurfaceAnimationThread.getHandler().post(
() -> mListener.onReady(mAnimationControl, typesReady));
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index b31c2e4..20216c3 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -167,6 +167,11 @@
if (aodChanged) {
// Ensure the new state takes effect.
mWindowManager.mWindowPlacerLocked.performSurfacePlacement();
+ // If the device can enter AOD and keyguard at the same time, the screen will not be
+ // turned off, so the snapshot needs to be refreshed when these states are changed.
+ if (aodShowing && keyguardShowing && keyguardChanged) {
+ mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY);
+ }
}
if (keyguardChanged) {
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 4ab5cd6..b1e12b6 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -77,7 +77,7 @@
final boolean shouldAttachNavBarToApp =
displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
&& service.getRecentsAnimationController() == null
- && displayContent.getFixedRotationAnimationController() == null;
+ && displayContent.getFadeRotationAnimationController() == null;
if (shouldAttachNavBarToApp) {
startNavigationBarWindowAnimation(
displayContent, durationHint, statusBarTransitionDelay, targets,
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 64ff108..bd84854 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -568,8 +568,9 @@
? mMinimizedHomeBounds
: null;
final Rect contentInsets;
- if (mTargetActivityRecord != null && mTargetActivityRecord.findMainWindow() != null) {
- contentInsets = mTargetActivityRecord.findMainWindow()
+ final WindowState targetAppMainWindow = getTargetAppMainWindow();
+ if (targetAppMainWindow != null) {
+ contentInsets = targetAppMainWindow
.getInsetsStateWithVisibilityOverride()
.calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(),
false /* ignoreVisibility */);
@@ -607,8 +608,8 @@
private void attachNavigationBarToApp() {
if (!mShouldAttachNavBarToAppDuringTransition
- // Skip the case where the nav bar is controlled by fixed rotation.
- || mDisplayContent.getFixedRotationAnimationController() != null) {
+ // Skip the case where the nav bar is controlled by fade rotation.
+ || mDisplayContent.getFadeRotationAnimationController() != null) {
return;
}
ActivityRecord topActivity = null;
@@ -1004,9 +1005,7 @@
boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle) {
// Update the input consumer touchable region to match the target app main window
- final WindowState targetAppMainWindow = mTargetActivityRecord != null
- ? mTargetActivityRecord.findMainWindow()
- : null;
+ final WindowState targetAppMainWindow = getTargetAppMainWindow();
if (targetAppMainWindow != null) {
targetAppMainWindow.getBounds(mTmpRect);
inputWindowHandle.touchableRegion.set(mTmpRect);
@@ -1026,6 +1025,13 @@
return mTargetActivityRecord.windowsCanBeWallpaperTarget();
}
+ WindowState getTargetAppMainWindow() {
+ if (mTargetActivityRecord == null) {
+ return null;
+ }
+ return mTargetActivityRecord.findMainWindow();
+ }
+
boolean isAnimatingTask(Task task) {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
if (task == mPendingAnimations.get(i).mTask) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 8915eba..5af44317 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -635,20 +635,7 @@
mHandler.post(() -> {
try {
synchronized (mService.mGlobalLock) {
- mTmpTasks.clear();
- mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
- // Since RecentsAnimation will handle task snapshot while switching apps
- // with the best capture timing (e.g. IME window capture), No need
- // additional task capture while task is controlled by RecentsAnimation.
- if (task.isVisible() && !task.isAnimatingByRecents()) {
- mTmpTasks.add(task);
- }
- });
- // Allow taking snapshot of home when turning screen off to reduce the delay of
- // waking from secure lock to home.
- final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY &&
- mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
- snapshotTasks(mTmpTasks, allowSnapshotHome);
+ snapshotForSleeping(displayId);
}
} finally {
listener.onScreenOff();
@@ -656,6 +643,27 @@
});
}
+ /** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */
+ void snapshotForSleeping(int displayId) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
+ mTmpTasks.clear();
+ mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
+ // Since RecentsAnimation will handle task snapshot while switching apps with the best
+ // capture timing (e.g. IME window capture), No need additional task capture while task
+ // is controlled by RecentsAnimation.
+ if (task.isVisible() && !task.isAnimatingByRecents()) {
+ mTmpTasks.add(task);
+ }
+ });
+ // Allow taking snapshot of home when turning screen off to reduce the delay of waking from
+ // secure lock to home.
+ final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY
+ && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
+ snapshotTasks(mTmpTasks, allowSnapshotHome);
+ }
+
/**
* @return The {@link Appearance} flags for the top fullscreen opaque window in the given
* {@param task}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 57394d6..6b1071c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -433,11 +433,6 @@
public static boolean sEnableRemoteKeyguardAnimation =
SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
- private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY =
- "ro.sf.disable_triple_buffer";
-
- static boolean sEnableTripleBuffering = !SystemProperties.getBoolean(
- DISABLE_TRIPLE_BUFFERING_PROPERTY, false);
/**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -1747,9 +1742,6 @@
if (mUseBLAST) {
res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
}
- if (sEnableTripleBuffering) {
- res |= WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING;
- }
if (displayContent.mCurrentFocus == null) {
displayContent.mWinAddedSinceNullFocus.add(win);
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 5ef9420..49e704f 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -1150,7 +1150,8 @@
FrameworkStatsLog.write(
FrameworkStatsLog.DEVICE_ROTATED,
event.timestamp,
- rotationToLogEnum(reportedRotation));
+ rotationToLogEnum(reportedRotation),
+ FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d88387..6da350b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1514,11 +1514,20 @@
}
void setOrientationChanging(boolean changing) {
- mOrientationChanging = changing;
mOrientationChangeTimedOut = false;
+ if (mOrientationChanging == changing) {
+ return;
+ }
+ mOrientationChanging = changing;
if (changing) {
mLastFreezeDuration = 0;
- mWmService.mRoot.mOrientationChangeComplete = false;
+ if (mWmService.mRoot.mOrientationChangeComplete
+ && mDisplayContent.waitForUnfreeze(this)) {
+ mWmService.mRoot.mOrientationChangeComplete = false;
+ }
+ } else {
+ // The orientation change is completed. If it was hidden by the animation, reshow it.
+ mDisplayContent.finishFadeRotationAnimation(this);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ebbebbb..0c80f86 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -656,8 +656,10 @@
if (w.getOrientationChanging()) {
if (!w.isDrawn()) {
- w.mWmService.mRoot.mOrientationChangeComplete = false;
- mAnimator.mLastWindowFreezeSource = w;
+ if (w.mDisplayContent.waitForUnfreeze(w)) {
+ w.mWmService.mRoot.mOrientationChangeComplete = false;
+ mAnimator.mLastWindowFreezeSource = w;
+ }
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation continue waiting for draw in %s", w);
} else {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5276d9c8..5163a43 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -758,11 +758,15 @@
/** @see WindowState#freezeInsetsState() */
void setInsetsFrozen(boolean freeze) {
- if (freeze) {
- forAllWindows(WindowState::freezeInsetsState, true /* traverseTopToBottom */);
- } else {
- forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */);
- }
+ forAllWindows(w -> {
+ if (w.mToken == this) {
+ if (freeze) {
+ w.freezeInsetsState();
+ } else {
+ w.clearFrozenInsetsState();
+ }
+ }
+ }, true /* traverseTopToBottom */);
}
@Override
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index f60b354..7f8168a 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -170,13 +170,13 @@
wrapper->hal()->off();
}
-static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jint amplitude) {
+static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jfloat amplitude) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorSetAmplitude failed because native wrapper was not initialized");
return;
}
- wrapper->hal()->setAmplitude(static_cast<int32_t>(amplitude));
+ wrapper->hal()->setAmplitude(static_cast<float>(amplitude));
}
static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong ptr,
@@ -313,9 +313,9 @@
{"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
{"on", "(JJJ)V", (void*)vibratorOn},
{"off", "(J)V", (void*)vibratorOff},
- {"setAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
+ {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
{"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
- {"performComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J",
+ {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
(void*)vibratorPerformComposedEffect},
{"getSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
{"getSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
@@ -334,11 +334,10 @@
jclass listenerClass = FindClassOrDie(env, listenerClassName);
sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V");
- jclass primitiveClass =
- FindClassOrDie(env, "android/os/VibrationEffect$Composition$PrimitiveEffect");
- sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I");
- sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F");
- sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I");
+ jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment");
+ sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I");
+ sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "mScale", "F");
+ sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "mDelay", "I");
return jniRegisterNativeMethods(env,
"com/android/server/vibrator/VibratorController$NativeWrapper",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index aed13b2..56e2385 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -139,12 +139,13 @@
private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
"admin-can-grant-sensors-permissions";
- private static final String TAG_NETWORK_SLICING_ENABLED = "network-slicing-enabled";
+ private static final String TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED =
+ "enterprise-network-preference-enabled";
private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
- private static final boolean NETWORK_SLICING_ENABLED_DEFAULT = true;
+ private static final boolean ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT = true;
DeviceAdminInfo info;
@@ -284,7 +285,8 @@
public String mOrganizationId;
public String mEnrollmentSpecificId;
public boolean mAdminCanGrantSensorsPermissions;
- public boolean mNetworkSlicingEnabled = NETWORK_SLICING_ENABLED_DEFAULT;
+ public boolean mEnterpriseNetworkPreferenceEnabled =
+ ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT;
private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
@@ -555,8 +557,9 @@
}
writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
mAdminCanGrantSensorsPermissions);
- if (mNetworkSlicingEnabled != NETWORK_SLICING_ENABLED_DEFAULT) {
- writeAttributeValueToXml(out, TAG_NETWORK_SLICING_ENABLED, mNetworkSlicingEnabled);
+ if (mEnterpriseNetworkPreferenceEnabled != ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT) {
+ writeAttributeValueToXml(out, TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED,
+ mEnterpriseNetworkPreferenceEnabled);
}
if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
@@ -784,9 +787,9 @@
mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
} else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false);
- } else if (TAG_NETWORK_SLICING_ENABLED.equals(tag)) {
- mNetworkSlicingEnabled = parser.getAttributeBoolean(
- null, ATTR_VALUE, NETWORK_SLICING_ENABLED_DEFAULT);
+ } else if (TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED.equals(tag)) {
+ mEnterpriseNetworkPreferenceEnabled = parser.getAttributeBoolean(
+ null, ATTR_VALUE, ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT);
} else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false);
} else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) {
@@ -1142,8 +1145,8 @@
pw.print("mAlwaysOnVpnLockdown=");
pw.println(mAlwaysOnVpnLockdown);
- pw.print("mNetworkSlicingEnabled=");
- pw.println(mNetworkSlicingEnabled);
+ pw.print("mEnterpriseNetworkPreferenceEnabled=");
+ pw.println(mEnterpriseNetworkPreferenceEnabled);
pw.print("mCommonCriteriaMode=");
pw.println(mCommonCriteriaMode);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 283895b..ca56e9b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3092,7 +3092,8 @@
boolean enableEnterpriseNetworkSlice = true;
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
- enableEnterpriseNetworkSlice = owner != null ? owner.mNetworkSlicingEnabled : true;
+ enableEnterpriseNetworkSlice = owner != null
+ ? owner.mEnterpriseNetworkPreferenceEnabled : true;
}
updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkSlice);
@@ -11423,30 +11424,32 @@
}
@Override
- public void setNetworkSlicingEnabled(boolean enabled) {
+ public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) {
if (!mHasFeature) {
return;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(isProfileOwner(caller),
- "Caller is not profile owner; only profile owner may control the network slicing");
+ "Caller is not profile owner;"
+ + " only profile owner may control the enterprise network preference");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
caller.getUserId());
- if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) {
- requiredAdmin.mNetworkSlicingEnabled = enabled;
+ if (requiredAdmin != null
+ && requiredAdmin.mEnterpriseNetworkPreferenceEnabled != enabled) {
+ requiredAdmin.mEnterpriseNetworkPreferenceEnabled = enabled;
saveSettingsLocked(caller.getUserId());
}
}
updateNetworkPreferenceForUser(caller.getUserId(), enabled);
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.SET_NETWORK_SLICING_ENABLED)
+ .createEvent(DevicePolicyEnums.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED)
.setBoolean(enabled)
.write();
}
@Override
- public boolean isNetworkSlicingEnabled(int userHandle) {
+ public boolean isEnterpriseNetworkPreferenceEnabled(int userHandle) {
if (!mHasFeature) {
return false;
}
@@ -11457,7 +11460,7 @@
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle);
if (requiredAdmin != null) {
- return requiredAdmin.mNetworkSlicingEnabled;
+ return requiredAdmin.mEnterpriseNetworkPreferenceEnabled;
} else {
return false;
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a88f2b4..932e997 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -377,6 +377,7 @@
dprintf(fd, "Mounts (%d): {\n", int(mMounts.size()));
for (auto&& [id, ifs] : mMounts) {
+ std::unique_lock ll(ifs->lock);
const IncFsMount& mnt = *ifs;
dprintf(fd, " [%d]: {\n", id);
if (id != mnt.mountId) {
@@ -422,6 +423,9 @@
}
bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+ if (!ifs.dataLoaderStub) {
+ return false;
+ }
if (ifs.dataLoaderStub->isSystemDataLoader()) {
return true;
}
@@ -439,6 +443,8 @@
std::lock_guard l(mLock);
mounts.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
+ std::unique_lock ll(ifs->lock);
+
if (ifs->mountId != id) {
continue;
}
@@ -456,7 +462,10 @@
std::thread([this, mounts = std::move(mounts)]() {
mJni->initializeForCurrentThread();
for (auto&& ifs : mounts) {
- ifs->dataLoaderStub->requestStart();
+ std::unique_lock l(ifs->lock);
+ if (ifs->dataLoaderStub) {
+ ifs->dataLoaderStub->requestStart();
+ }
}
}).detach();
}
@@ -671,23 +680,36 @@
setUidReadTimeouts(storageId, std::move(perUidReadTimeouts));
}
- // Re-initialize DataLoader.
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
- if (!ifs) {
- return false;
- }
- if (ifs->dataLoaderStub) {
- ifs->dataLoaderStub->cleanupResources();
- ifs->dataLoaderStub = {};
- }
- l.unlock();
+ IfsMountPtr ifs;
+ DataLoaderStubPtr dataLoaderStub;
- // DataLoader.
- auto dataLoaderStub =
- prepareDataLoader(*ifs, std::move(dataLoaderParams), std::move(statusListener),
- healthCheckParams, std::move(healthListener));
- CHECK(dataLoaderStub);
+ // Re-initialize DataLoader.
+ {
+ ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
+
+ std::unique_lock l(ifs->lock);
+ dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+ }
+
+ if (dataLoaderStub) {
+ dataLoaderStub->cleanupResources();
+ dataLoaderStub = {};
+ }
+
+ {
+ std::unique_lock l(ifs->lock);
+ if (ifs->dataLoaderStub) {
+ LOG(INFO) << "Skipped data loader stub creation because it already exists";
+ return false;
+ }
+ prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams), std::move(statusListener),
+ healthCheckParams, std::move(healthListener));
+ CHECK(ifs->dataLoaderStub);
+ dataLoaderStub = ifs->dataLoaderStub;
+ }
if (dataLoaderStub->isSystemDataLoader()) {
// Readlogs from system dataloader (adb) can always be collected.
@@ -705,13 +727,14 @@
<< storageId;
return;
}
+ std::unique_lock l(ifs->lock);
if (ifs->startLoadingTs != startLoadingTs) {
LOG(INFO) << "Can't disable the readlogs, timestamp mismatch (new "
"installation?): "
<< storageId;
return;
}
- setStorageParams(*ifs, storageId, /*enableReadLogs=*/false);
+ disableReadLogsLocked(*ifs);
});
}
@@ -733,17 +756,17 @@
}
void IncrementalService::disallowReadLogs(StorageId storageId) {
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
+ const auto ifs = getIfs(storageId);
if (!ifs) {
LOG(ERROR) << "disallowReadLogs failed, invalid storageId: " << storageId;
return;
}
+
+ std::unique_lock l(ifs->lock);
if (!ifs->readLogsAllowed()) {
return;
}
ifs->disallowReadLogs();
- l.unlock();
const auto metadata = constants().readLogsDisabledMarkerName;
if (auto err = mIncFs->makeFile(ifs->control,
@@ -755,7 +778,7 @@
return;
}
- setStorageParams(storageId, /*enableReadLogs=*/false);
+ disableReadLogsLocked(*ifs);
}
int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
@@ -764,61 +787,66 @@
LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
return -EINVAL;
}
- return setStorageParams(*ifs, storageId, enableReadLogs);
-}
-int IncrementalService::setStorageParams(IncFsMount& ifs, StorageId storageId,
- bool enableReadLogs) {
- const auto& params = ifs.dataLoaderStub->params();
- if (enableReadLogs) {
- if (!ifs.readLogsAllowed()) {
- LOG(ERROR) << "setStorageParams failed, readlogs disallowed for storageId: "
- << storageId;
- return -EPERM;
- }
-
- // Check loader usage stats permission and apop.
- if (auto status = mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage,
- params.packageName.c_str());
- !status.isOk()) {
- LOG(ERROR) << " Permission: " << kLoaderUsageStats
- << " check failed: " << status.toString8();
- return fromBinderStatus(status);
- }
-
- // Check multiuser permission.
- if (auto status = mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr,
- params.packageName.c_str());
- !status.isOk()) {
- LOG(ERROR) << " Permission: " << kInteractAcrossUsers
- << " check failed: " << status.toString8();
- return fromBinderStatus(status);
- }
-
- // Check installation time.
- const auto now = mClock->now();
- const auto startLoadingTs = ifs.startLoadingTs;
- if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
- LOG(ERROR) << "setStorageParams failed, readlogs can't be enabled at this time, "
- "storageId: "
- << storageId;
- return -EPERM;
- }
+ std::unique_lock l(ifs->lock);
+ if (!enableReadLogs) {
+ return disableReadLogsLocked(*ifs);
}
- if (auto status = applyStorageParams(ifs, enableReadLogs); !status.isOk()) {
- LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
+ if (!ifs->readLogsAllowed()) {
+ LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
+ return -EPERM;
+ }
+
+ if (!ifs->dataLoaderStub) {
+ // This should never happen - only DL can call enableReadLogs.
+ LOG(ERROR) << "enableReadLogs failed: invalid state";
+ return -EPERM;
+ }
+
+ // Check installation time.
+ const auto now = mClock->now();
+ const auto startLoadingTs = ifs->startLoadingTs;
+ if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
+ LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
+ << storageId;
+ return -EPERM;
+ }
+
+ const auto& packageName = ifs->dataLoaderStub->params().packageName;
+
+ // Check loader usage stats permission and apop.
+ if (auto status =
+ mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage, packageName.c_str());
+ !status.isOk()) {
+ LOG(ERROR) << " Permission: " << kLoaderUsageStats
+ << " check failed: " << status.toString8();
return fromBinderStatus(status);
}
- if (enableReadLogs) {
- registerAppOpsCallback(params.packageName);
+ // Check multiuser permission.
+ if (auto status =
+ mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr, packageName.c_str());
+ !status.isOk()) {
+ LOG(ERROR) << " Permission: " << kInteractAcrossUsers
+ << " check failed: " << status.toString8();
+ return fromBinderStatus(status);
}
+ if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) {
+ return status;
+ }
+
+ registerAppOpsCallback(packageName);
+
return 0;
}
-binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enableReadLogs) {
+int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) {
+ return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false);
+}
+
+int IncrementalService::applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs) {
os::incremental::IncrementalFileSystemControlParcel control;
control.cmd.reset(dup(ifs.control.cmd()));
control.pendingReads.reset(dup(ifs.control.pendingReads()));
@@ -832,8 +860,10 @@
if (status.isOk()) {
// Store enabled state.
ifs.setReadLogsEnabled(enableReadLogs);
+ } else {
+ LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
}
- return status;
+ return status.isOk() ? 0 : fromBinderStatus(status);
}
void IncrementalService::deleteStorage(StorageId storageId) {
@@ -1224,9 +1254,14 @@
return;
}
- const auto timeout = std::chrono::duration_cast<milliseconds>(maxPendingTimeUs) -
- Constants::perUidTimeoutOffset;
- updateUidReadTimeouts(storage, Clock::now() + timeout);
+ const auto timeout = Clock::now() + maxPendingTimeUs - Constants::perUidTimeoutOffset;
+ addIfsStateCallback(storage, [this, timeout](StorageId storageId, IfsState state) -> bool {
+ if (checkUidReadTimeouts(storageId, state, timeout)) {
+ return true;
+ }
+ clearUidReadTimeouts(storageId);
+ return false;
+ });
}
void IncrementalService::clearUidReadTimeouts(StorageId storage) {
@@ -1234,39 +1269,32 @@
if (!ifs) {
return;
}
-
mIncFs->setUidReadTimeouts(ifs->control, {});
}
-void IncrementalService::updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit) {
- // Reached maximum timeout.
+bool IncrementalService::checkUidReadTimeouts(StorageId storage, IfsState state,
+ Clock::time_point timeLimit) {
if (Clock::now() >= timeLimit) {
- return clearUidReadTimeouts(storage);
+ // Reached maximum timeout.
+ return false;
+ }
+ if (state.error) {
+ // Something is wrong, abort.
+ return false;
}
// Still loading?
- const auto state = isMountFullyLoaded(storage);
- if (int(state) < 0) {
- // Something is wrong, abort.
- return clearUidReadTimeouts(storage);
- }
-
- if (state == incfs::LoadingState::Full) {
- // Fully loaded, check readLogs collection.
- const auto ifs = getIfs(storage);
- if (!ifs->readLogsEnabled()) {
- return clearUidReadTimeouts(storage);
- }
+ if (state.fullyLoaded && !state.readLogsEnabled) {
+ return false;
}
const auto timeLeft = timeLimit - Clock::now();
if (timeLeft < Constants::progressUpdateInterval) {
// Don't bother.
- return clearUidReadTimeouts(storage);
+ return false;
}
- addTimedJob(*mTimedQueue, storage, Constants::progressUpdateInterval,
- [this, storage, timeLimit]() { updateUidReadTimeouts(storage, timeLimit); });
+ return true;
}
std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() {
@@ -1533,7 +1561,7 @@
dataLoaderParams.arguments = loader.arguments();
}
- prepareDataLoader(*ifs, std::move(dataLoaderParams));
+ prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams));
CHECK(ifs->dataLoaderStub);
std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
@@ -1615,24 +1643,10 @@
}
}
-IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
- IncFsMount& ifs, DataLoaderParamsParcel&& params, DataLoaderStatusListener&& statusListener,
- const StorageHealthCheckParams& healthCheckParams, StorageHealthListener&& healthListener) {
- std::unique_lock l(ifs.lock);
- prepareDataLoaderLocked(ifs, std::move(params), std::move(statusListener), healthCheckParams,
- std::move(healthListener));
- return ifs.dataLoaderStub;
-}
-
void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
DataLoaderStatusListener&& statusListener,
const StorageHealthCheckParams& healthCheckParams,
StorageHealthListener&& healthListener) {
- if (ifs.dataLoaderStub) {
- LOG(INFO) << "Skipped data loader preparation because it already exists";
- return;
- }
-
FileSystemControlParcel fsControlParcel;
fsControlParcel.incremental = std::make_optional<IncrementalFileSystemControlParcel>();
fsControlParcel.incremental->cmd.reset(dup(ifs.control.cmd()));
@@ -1647,6 +1661,29 @@
new DataLoaderStub(*this, ifs.mountId, std::move(params), std::move(fsControlParcel),
std::move(statusListener), healthCheckParams,
std::move(healthListener), path::join(ifs.root, constants().mount));
+
+ addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
+ if (!state.fullyLoaded || state.readLogsEnabled) {
+ return true;
+ }
+
+ DataLoaderStubPtr dataLoaderStub;
+ {
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
+
+ std::unique_lock l(ifs->lock);
+ dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+ }
+
+ if (dataLoaderStub) {
+ dataLoaderStub->cleanupResources();
+ }
+
+ return false;
+ });
}
template <class Duration>
@@ -2070,11 +2107,11 @@
StorageHealthListener healthListener) {
DataLoaderStubPtr dataLoaderStub;
{
- std::unique_lock l(mLock);
- const auto& ifs = getIfsLocked(storage);
+ const auto& ifs = getIfs(storage);
if (!ifs) {
return false;
}
+ std::unique_lock l(ifs->lock);
dataLoaderStub = ifs->dataLoaderStub;
if (!dataLoaderStub) {
return false;
@@ -2160,13 +2197,16 @@
std::lock_guard l(mLock);
affected.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
- if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) {
+ std::unique_lock ll(ifs->lock);
+
+ if (ifs->mountId == id && ifs->dataLoaderStub &&
+ ifs->dataLoaderStub->params().packageName == packageName) {
affected.push_back(ifs);
}
}
}
for (auto&& ifs : affected) {
- applyStorageParams(*ifs, false);
+ applyStorageParamsLocked(*ifs, /*enableReadLogs=*/false);
}
}
@@ -2187,6 +2227,101 @@
return true;
}
+void IncrementalService::addIfsStateCallback(StorageId storageId, IfsStateCallback callback) {
+ bool wasEmpty;
+ {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ wasEmpty = mIfsStateCallbacks.empty();
+ mIfsStateCallbacks[storageId].emplace_back(std::move(callback));
+ }
+ if (wasEmpty) {
+ addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ [this]() { processIfsStateCallbacks(); });
+ }
+}
+
+void IncrementalService::processIfsStateCallbacks() {
+ StorageId storageId = kInvalidStorageId;
+ std::vector<IfsStateCallback> local;
+ while (true) {
+ {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ if (mIfsStateCallbacks.empty()) {
+ return;
+ }
+ IfsStateCallbacks::iterator it;
+ if (storageId == kInvalidStorageId) {
+ // First entry, initialize the it.
+ it = mIfsStateCallbacks.begin();
+ } else {
+ // Subsequent entries, update the storageId, and shift to the new one.
+ it = mIfsStateCallbacks.find(storageId);
+ if (it == mIfsStateCallbacks.end()) {
+ // Was removed while processing, too bad.
+ break;
+ }
+
+ auto& callbacks = it->second;
+ if (callbacks.empty()) {
+ std::swap(callbacks, local);
+ } else {
+ callbacks.insert(callbacks.end(), local.begin(), local.end());
+ }
+ if (callbacks.empty()) {
+ it = mIfsStateCallbacks.erase(it);
+ if (mIfsStateCallbacks.empty()) {
+ return;
+ }
+ } else {
+ ++it;
+ }
+ }
+
+ if (it == mIfsStateCallbacks.end()) {
+ break;
+ }
+
+ storageId = it->first;
+ auto& callbacks = it->second;
+ if (callbacks.empty()) {
+ // Invalid case, one extra lookup should be ok.
+ continue;
+ }
+ std::swap(callbacks, local);
+ }
+
+ processIfsStateCallbacks(storageId, local);
+ }
+
+ addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ [this]() { processIfsStateCallbacks(); });
+}
+
+void IncrementalService::processIfsStateCallbacks(StorageId storageId,
+ std::vector<IfsStateCallback>& callbacks) {
+ const auto state = isMountFullyLoaded(storageId);
+ IfsState storageState = {};
+ storageState.error = int(state) < 0;
+ storageState.fullyLoaded = state == incfs::LoadingState::Full;
+ if (storageState.fullyLoaded) {
+ const auto ifs = getIfs(storageId);
+ storageState.readLogsEnabled = ifs && ifs->readLogsEnabled();
+ }
+
+ for (auto cur = callbacks.begin(); cur != callbacks.end();) {
+ if ((*cur)(storageId, storageState)) {
+ ++cur;
+ } else {
+ cur = callbacks.erase(cur);
+ }
+ }
+}
+
+void IncrementalService::removeIfsStateCallbacks(StorageId storageId) {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ mIfsStateCallbacks.erase(storageId);
+}
+
void IncrementalService::getMetrics(StorageId storageId, android::os::PersistableBundle* result) {
const auto duration = getMillsSinceOldestPendingRead(storageId);
if (duration >= 0) {
@@ -2197,12 +2332,12 @@
}
long IncrementalService::getMillsSinceOldestPendingRead(StorageId storageId) {
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
+ const auto ifs = getIfs(storageId);
if (!ifs) {
LOG(ERROR) << "getMillsSinceOldestPendingRead failed, invalid storageId: " << storageId;
return -EINVAL;
}
+ std::unique_lock l(ifs->lock);
if (!ifs->dataLoaderStub) {
LOG(ERROR) << "getMillsSinceOldestPendingRead failed, no data loader: " << storageId;
return -EINVAL;
@@ -2248,6 +2383,7 @@
resetHealthControl();
mService.removeTimedJobs(*mService.mTimedQueue, mId);
}
+ mService.removeIfsStateCallbacks(mId);
requestDestroy();
@@ -2758,7 +2894,7 @@
mService.mLooper->addFd(
pendingReadsFd, android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
[](int, int, void* data) -> int {
- auto&& self = (DataLoaderStub*)data;
+ auto self = (DataLoaderStub*)data;
self->updateHealthStatus(/*baseline=*/true);
return 0;
},
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 4a5db06..a8f32de 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -74,6 +74,17 @@
using PerUidReadTimeouts = ::android::os::incremental::PerUidReadTimeouts;
+struct IfsState {
+ // If mount is fully loaded.
+ bool fullyLoaded = false;
+ // If read logs are enabled on this mount. Populated only if fullyLoaded == true.
+ bool readLogsEnabled = false;
+ // If there was an error fetching any of the above.
+ bool error = false;
+};
+// Returns true if wants to be called again.
+using IfsStateCallback = std::function<bool(StorageId, IfsState)>;
+
class IncrementalService final {
public:
explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir);
@@ -366,7 +377,7 @@
void setUidReadTimeouts(StorageId storage,
std::vector<PerUidReadTimeouts>&& perUidReadTimeouts);
void clearUidReadTimeouts(StorageId storage);
- void updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit);
+ bool checkUidReadTimeouts(StorageId storage, IfsState state, Clock::time_point timeLimit);
std::unordered_set<std::string_view> adoptMountedInstances();
void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames);
@@ -387,11 +398,6 @@
bool needStartDataLoaderLocked(IncFsMount& ifs);
- DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
- content::pm::DataLoaderParamsParcel&& params,
- DataLoaderStatusListener&& statusListener = {},
- const StorageHealthCheckParams& healthCheckParams = {},
- StorageHealthListener&& healthListener = {});
void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params,
DataLoaderStatusListener&& statusListener = {},
const StorageHealthCheckParams& healthCheckParams = {},
@@ -410,8 +416,8 @@
std::string_view path) const;
int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode);
- int setStorageParams(IncFsMount& ifs, StorageId storageId, bool enableReadLogs);
- binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
+ int disableReadLogsLocked(IncFsMount& ifs);
+ int applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs);
LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
@@ -431,6 +437,12 @@
bool addTimedJob(TimedQueueWrapper& timedQueue, MountId id, Milliseconds after, Job what);
bool removeTimedJobs(TimedQueueWrapper& timedQueue, MountId id);
+
+ void addIfsStateCallback(StorageId storageId, IfsStateCallback callback);
+ void removeIfsStateCallbacks(StorageId storageId);
+ void processIfsStateCallbacks();
+ void processIfsStateCallbacks(StorageId storageId, std::vector<IfsStateCallback>& callbacks);
+
bool updateLoadingProgress(int32_t storageId,
StorageLoadingProgressListener&& progressListener);
long getMillsSinceOldestPendingRead(StorageId storage);
@@ -456,6 +468,10 @@
std::mutex mCallbacksLock;
std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered;
+ using IfsStateCallbacks = std::unordered_map<StorageId, std::vector<IfsStateCallback>>;
+ std::mutex mIfsStateCallbacksLock;
+ IfsStateCallbacks mIfsStateCallbacks;
+
std::atomic_bool mSystemReady = false;
StorageId mNextId = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 54bc95d..de8822d 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -140,9 +140,7 @@
const content::pm::FileSystemControlParcel& control,
const sp<content::pm::IDataLoaderStatusListener>& listener) {
createOkNoStatus(id, params, control, listener);
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_CREATED);
- }
+ reportStatus(id);
return binder::Status::ok();
}
binder::Status createOkNoStatus(int32_t id, const content::pm::DataLoaderParamsParcel& params,
@@ -150,33 +148,26 @@
const sp<content::pm::IDataLoaderStatusListener>& listener) {
mServiceConnector = control.service;
mListener = listener;
+ mStatus = IDataLoaderStatusListener::DATA_LOADER_CREATED;
return binder::Status::ok();
}
binder::Status startOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
return binder::Status::ok();
}
binder::Status stopOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
return binder::Status::ok();
}
binder::Status destroyOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
mListener = nullptr;
return binder::Status::ok();
}
binder::Status prepareImageOk(int32_t id,
const ::std::vector<content::pm::InstallationFileParcel>&,
const ::std::vector<::std::string>&) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
return binder::Status::ok();
}
binder::Status storageError(int32_t id) {
@@ -197,10 +188,22 @@
EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk());
return result;
}
+ int status() const { return mStatus; }
private:
+ void setAndReportStatus(int id, int status) {
+ mStatus = status;
+ reportStatus(id);
+ }
+ void reportStatus(int id) {
+ if (mListener) {
+ mListener->onStatusChanged(id, mStatus);
+ }
+ }
+
sp<IIncrementalServiceConnector> mServiceConnector;
sp<IDataLoaderStatusListener> mListener;
+ int mStatus = IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
};
class MockDataLoaderManager : public DataLoaderManagerWrapper {
@@ -915,7 +918,7 @@
EXPECT_CALL(*mDataLoader, start(_)).Times(6);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(3);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1126,7 +1129,7 @@
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(2);
EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(2);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(5);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(6);
sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
NiceMock<MockStorageHealthListener>* listenerMock = listener.get();
@@ -1314,7 +1317,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1355,7 +1358,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
// System data loader.
mDataLoaderParcel.packageName = "android";
TemporaryDir tempDir;
@@ -1366,9 +1369,9 @@
ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
{}, {}));
- // No readlogs callback.
- ASSERT_EQ(mTimedQueue->mAfter, 0ms);
- ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+ // IfsState callback.
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
// Now advance clock for 1hr.
@@ -1376,6 +1379,8 @@
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
// Now advance clock for 2hrs.
mClock->advance(readLogsMaxInterval);
+ // IfsStorage callback should not affect anything.
+ callback();
ASSERT_EQ(mDataLoader->setStorageParams(true), 0);
}
@@ -1394,7 +1399,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1685,6 +1690,144 @@
mIncrementalService->registerLoadingProgressListener(storageId, listener);
}
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) {
+ mFs->hasFiles();
+
+ const auto stateUpdateInterval = 1s;
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ // No unbinding just yet.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // System data loader to get rid of readlog timeout callback.
+ mDataLoaderParcel.packageName = "android";
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // IfsState callback present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Not loaded yet.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+ // Send the callback, should not do anything.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Fully loaded.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)).WillOnce(Return(incfs::LoadingState::Full));
+ // Expect the unbind.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+
+ callback();
+
+ // Destroyed.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) {
+ mFs->hasFiles();
+
+ // Readlogs.
+ mVold->setIncFsMountOptionsSuccess();
+ mAppOpsManager->checkPermissionSuccess();
+
+ const auto stateUpdateInterval = 1s;
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ // No unbinding just yet.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // System data loader to get rid of readlog timeout callback.
+ mDataLoaderParcel.packageName = "android";
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // IfsState callback present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Not loaded yet.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+ // Send the callback, should not do anything.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Fully loaded.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::Full))
+ .WillOnce(Return(incfs::LoadingState::Full));
+ // But with readlogs.
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+
+ // Send the callback, still nothing.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+ // Disable readlogs and expect the unbind.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ ASSERT_GE(mDataLoader->setStorageParams(false), 0);
+
+ callback();
+
+ // Destroyed.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) {
mIncFs->openMountSuccess();
sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
@@ -1801,7 +1944,7 @@
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
int storageId =
@@ -1829,7 +1972,6 @@
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
.WillOnce(Return(incfs::LoadingState::MissingBlocks))
.WillOnce(Return(incfs::LoadingState::MissingBlocks))
- .WillOnce(Return(incfs::LoadingState::Full))
.WillOnce(Return(incfs::LoadingState::Full));
// Mark DataLoader as 'system' so that readlogs don't pollute the timed queue.
@@ -1846,10 +1988,10 @@
{
// Timed callback present -> 0 progress.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
// Call it again.
timedCallback();
@@ -1857,10 +1999,10 @@
{
// Still present -> some progress.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
// Fully loaded but readlogs collection enabled.
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
@@ -1871,10 +2013,10 @@
{
// Still present -> fully loaded + readlogs.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
// Now disable readlogs.
ASSERT_GE(mDataLoader->setStorageParams(false), 0);
diff --git a/services/net/Android.bp b/services/net/Android.bp
index b01e425..800f7ad 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -83,3 +83,15 @@
"//packages/modules/Connectivity/Tethering"
],
}
+
+filegroup {
+ name: "services-connectivity-shared-srcs",
+ srcs: [
+ // TODO: move to networkstack-client
+ "java/android/net/IpMemoryStore.java",
+ "java/android/net/NetworkMonitorManager.java",
+ // TODO: move to libs/net
+ "java/android/net/util/KeepalivePacketDataUtil.java",
+ "java/android/net/util/NetworkConstants.java",
+ ],
+}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index e7d0121..eab3b77 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -388,9 +388,11 @@
private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
@Override
- public void onCreatePredictionSession(AppPredictionContext context,
+ public void onCreatePredictionSession(AppPredictionContext appPredictionContext,
AppPredictionSessionId sessionId) {
- mSessions.put(sessionId, new SessionInfo(context, mDataManager, sessionId.getUserId()));
+ mSessions.put(sessionId,
+ new SessionInfo(appPredictionContext, mDataManager, sessionId.getUserId(),
+ getContext()));
}
@Override
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java
index 28612f1..d256d9c 100644
--- a/services/people/java/com/android/server/people/SessionInfo.java
+++ b/services/people/java/com/android/server/people/SessionInfo.java
@@ -20,6 +20,7 @@
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppTarget;
import android.app.prediction.IPredictionCallback;
+import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -40,9 +41,9 @@
new RemoteCallbackList<>();
SessionInfo(AppPredictionContext predictionContext, DataManager dataManager,
- @UserIdInt int callingUserId) {
+ @UserIdInt int callingUserId, Context context) {
mAppTargetPredictor = AppTargetPredictor.create(predictionContext,
- this::updatePredictions, dataManager, callingUserId);
+ this::updatePredictions, dataManager, callingUserId, context);
}
void addCallback(IPredictionCallback callback) {
diff --git a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
index c89dadc..e191081 100644
--- a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
@@ -24,6 +24,7 @@
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.Context;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.people.data.DataManager;
@@ -43,10 +44,10 @@
/** Creates a {@link AppTargetPredictor} instance based on the prediction context. */
public static AppTargetPredictor create(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId, Context context) {
if (UI_SURFACE_SHARE.equals(predictionContext.getUiSurface())) {
- return new ShareTargetPredictor(
- predictionContext, updatePredictionsMethod, dataManager, callingUserId);
+ return new ShareTargetPredictor(predictionContext, updatePredictionsMethod, dataManager,
+ callingUserId, context);
}
return new AppTargetPredictor(
predictionContext, updatePredictionsMethod, dataManager, callingUserId);
@@ -124,6 +125,11 @@
callback.accept(targets);
}
+ /** To be overridden by the subclass to recycle resources. */
+ @WorkerThread
+ void destroy() {
+ }
+
AppPredictionContext getPredictionContext() {
return mPredictionContext;
}
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 236ac84..368b737 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -16,6 +16,8 @@
package com.android.server.people.prediction;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
import static java.util.Collections.reverseOrder;
import android.annotation.NonNull;
@@ -23,17 +25,23 @@
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
@@ -52,14 +60,27 @@
private static final String TAG = "ShareTargetPredictor";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
private final IntentFilter mIntentFilter;
+ private final AppPredictor mRemoteAppPredictor;
ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager,
+ @UserIdInt int callingUserId, @NonNull Context context) {
super(predictionContext, updatePredictionsMethod, dataManager, callingUserId);
mIntentFilter = predictionContext.getExtras().getParcelable(
ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
+ if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ false)) {
+ predictionContext.getExtras().putBoolean(REMOTE_APP_PREDICTOR_KEY, true);
+ mRemoteAppPredictor = context.createContextAsUser(UserHandle.of(callingUserId), 0)
+ .getSystemService(AppPredictionManager.class)
+ .createAppPredictionSession(predictionContext);
+ } else {
+ mRemoteAppPredictor = null;
+ }
}
/** Reports chosen history of direct/app share targets. */
@@ -72,6 +93,9 @@
if (mIntentFilter != null) {
getDataManager().reportShareTargetEvent(event, mIntentFilter);
}
+ if (mRemoteAppPredictor != null) {
+ mRemoteAppPredictor.notifyAppTargetEvent(event);
+ }
}
/** Provides prediction on direct share targets */
@@ -129,6 +153,15 @@
callback.accept(appTargetList);
}
+ /** Recycles resources. */
+ @WorkerThread
+ @Override
+ void destroy() {
+ if (mRemoteAppPredictor != null) {
+ mRemoteAppPredictor.destroy();
+ }
+ }
+
private List<ShareTarget> getDirectShareTargets() {
List<ShareTarget> shareTargets = new ArrayList<>();
List<ShareShortcutInfo> shareShortcuts =
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index d5eda20..dce853a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -41,7 +41,12 @@
}
private val platformCompat: PlatformCompat = mockThrowOnUnmocked {
- whenever(isChangeEnabled(eq(DomainVerificationCollector.RESTRICT_DOMAINS), any())) {
+ whenever(
+ isChangeEnabledInternalNoLogging(
+ eq(DomainVerificationCollector.RESTRICT_DOMAINS),
+ any()
+ )
+ ) {
(arguments[1] as ApplicationInfo).targetSdkVersion >= Build.VERSION_CODES.S
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 7e25901..1b0a305 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -114,12 +114,7 @@
it,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
- whenever(
- isChangeEnabled(
- anyLong(),
- any()
- )
- ) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(connection)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 0e74b65..ef79b08 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -310,7 +310,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index fe3672d..0ce16e6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -43,6 +43,7 @@
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import java.util.UUID
@@ -366,7 +367,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 377bae1..b7c6922 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -98,7 +98,7 @@
context,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(),any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(connection)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 44c1b8f..54648ab 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -71,7 +71,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 7925b69..0fcda81 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -660,15 +660,19 @@
new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
markImplicitConstraintsSatisfied(job, false);
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), false, false);
assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
markImplicitConstraintsSatisfied(job, true);
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), false, false);
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
}
@@ -677,7 +681,7 @@
job.setDeviceNotDozingConstraintSatisfied(
sElapsedRealtimeClock.millis(), isSatisfied, false);
job.setBackgroundNotRestrictedConstraintSatisfied(
- sElapsedRealtimeClock.millis(), isSatisfied);
+ sElapsedRealtimeClock.millis(), isSatisfied, false);
}
private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index f73af53..ee1a4f4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -384,7 +384,8 @@
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
/* state */ true, /* allowlisted */false);
- js.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ js.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
return js;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
index 0597443..ef48646 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
@@ -19,8 +19,6 @@
import android.os.IPowerManager;
import android.os.PowerManager.LocationPowerSaveMode;
-import com.android.server.location.eventlog.LocationEventLog;
-
/**
* Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
* change".
@@ -30,8 +28,7 @@
@LocationPowerSaveMode
private int mLocationPowerSaveMode;
- public FakeLocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
- super(locationEventLog);
+ public FakeLocationPowerSaveModeHelper() {
mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
index 6156ba9..28da027 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
@@ -43,7 +43,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
import org.junit.After;
@@ -85,7 +84,7 @@
Context context = mock(Context.class);
doReturn(powerManager).when(context).getSystemService(PowerManager.class);
- mHelper = new SystemLocationPowerSaveModeHelper(context, new LocationEventLog());
+ mHelper = new SystemLocationPowerSaveModeHelper(context);
mHelper.onSystemReady();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 1f102ac..ae70dad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -16,8 +16,6 @@
package com.android.server.location.injector;
-import com.android.server.location.eventlog.LocationEventLog;
-
public class TestInjector implements Injector {
private final FakeUserInfoHelper mUserInfoHelper;
@@ -35,17 +33,13 @@
private final LocationUsageLogger mLocationUsageLogger;
public TestInjector() {
- this(new LocationEventLog());
- }
-
- public TestInjector(LocationEventLog eventLog) {
mUserInfoHelper = new FakeUserInfoHelper();
mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
mAppForegroundHelper = new FakeAppForegroundHelper();
- mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
+ mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper();
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
mDeviceIdleHelper = new FakeDeviceIdleHelper();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 1b58e92..24b85f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -83,7 +83,6 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
@@ -161,19 +160,17 @@
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
- LocationEventLog eventLog = new LocationEventLog();
-
- mInjector = new TestInjector(eventLog);
+ mInjector = new TestInjector();
mInjector.getUserInfoHelper().startUser(OTHER_USER);
- mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog);
+ mPassive = new PassiveLocationProviderManager(mContext, mInjector);
mPassive.startManager();
mPassive.setRealProvider(new PassiveLocationProvider(mContext));
mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY);
mProvider.setProviderAllowed(true);
- mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive);
+ mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
mManager.startManager();
mManager.setRealProvider(mProvider);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index c3cca64..04e0151 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -36,7 +36,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.test.FakeProvider;
@@ -77,7 +76,7 @@
mDelegateProvider = new FakeProvider(mDelegate);
mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
- mDelegateProvider, new LocationEventLog());
+ mDelegateProvider);
mProvider.getController().setListener(mListener);
mProvider.getController().start();
}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 1c96838..1b8ab21 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.ArgumentMatchers.any;
@@ -67,6 +68,7 @@
private static final String PACKAGE_SCHEME = "package";
private static final String PACKAGE_NAME_1 = "package1";
private static final String PACKAGE_NAME_2 = "package2";
+ private static final String PACKAGE_NAME_3 = "package3";
private static final int USER_ID_1 = 1;
private static final int USER_ID_2 = 2;
@@ -107,6 +109,8 @@
List<PackageInfo> packages = new ArrayList<>();
packages.add(makePackageInfo(PACKAGE_NAME_1));
+ packages.add(makePackageInfo(PACKAGE_NAME_2));
+ packages.add(makePackageInfo(PACKAGE_NAME_3));
doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt());
mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -179,6 +183,26 @@
assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
}
+ @Test
+ public void testGetHibernatingPackagesForUser_returnsCorrectPackages() throws RemoteException {
+ // GIVEN an unlocked user with all packages installed
+ UserInfo userInfo =
+ addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
+ doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+
+ // WHEN packages are hibernated for the user
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true);
+
+ // THEN the hibernating packages returned matches
+ List<String> hibernatingPackages =
+ mAppHibernationService.getHibernatingPackagesForUser(USER_ID_2);
+ assertEquals(2, hibernatingPackages.size());
+ assertTrue(hibernatingPackages.contains(PACKAGE_NAME_1));
+ assertTrue(hibernatingPackages.contains(PACKAGE_NAME_2));
+ }
+
/**
* Add a mock user with one package.
*/
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
new file mode 100644
index 0000000..6cdac1a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.biometrics.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class UserAwareBiometricSchedulerTest {
+
+ private static final String TAG = "BiometricSchedulerTest";
+ private static final int TEST_SENSOR_ID = 0;
+
+ private UserAwareBiometricScheduler mScheduler;
+ private IBinder mToken;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private IBiometricService mBiometricService;
+
+ private TestUserStartedCallback mUserStartedCallback;
+ private TestUserStoppedCallback mUserStoppedCallback;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mToken = new Binder();
+ mUserStartedCallback = new TestUserStartedCallback();
+ mUserStoppedCallback = new TestUserStoppedCallback();
+
+ mScheduler = new UserAwareBiometricScheduler(TAG,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ () -> mCurrentUserId,
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new TestStopUserClient(mContext, Object::new, mToken, userId,
+ TEST_SENSOR_ID, mUserStoppedCallback);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?> getStartUserClient(int newUserId) {
+ return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
+ TEST_SENSOR_ID, mUserStartedCallback);
+ }
+ });
+ }
+
+ @Test
+ public void testScheduleOperation_whenNoUser() {
+ mCurrentUserId = UserHandle.USER_NULL;
+
+ final int nextUserId = 0;
+
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+ verify(nextClient, never()).start(any());
+ assertEquals(0, mUserStoppedCallback.numInvocations);
+ assertEquals(1, mUserStartedCallback.numInvocations);
+
+ waitForIdle();
+ verify(nextClient).start(any());
+ }
+
+ @Test
+ public void testScheduleOperation_whenSameUser() {
+ mCurrentUserId = 10;
+
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+
+ waitForIdle();
+
+ verify(nextClient).start(any());
+ assertEquals(0, mUserStoppedCallback.numInvocations);
+ assertEquals(0, mUserStartedCallback.numInvocations);
+ }
+
+ @Test
+ public void testScheduleOperation_whenDifferentUser() {
+ mCurrentUserId = 10;
+
+ final int nextUserId = 11;
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+
+ waitForIdle();
+ assertEquals(1, mUserStoppedCallback.numInvocations);
+
+ waitForIdle();
+ assertEquals(1, mUserStartedCallback.numInvocations);
+
+ waitForIdle();
+ verify(nextClient).start(any());
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
+
+ int numInvocations;
+
+ @Override
+ public void onUserStopped() {
+ numInvocations++;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ }
+
+ private class TestUserStartedCallback implements StartUserClient.UserStartedCallback {
+
+ int numInvocations;
+
+ @Override
+ public void onUserStarted(int newUserId) {
+ numInvocations++;
+ mCurrentUserId = newUserId;
+ }
+ }
+
+ private static class TestStopUserClient extends StopUserClient<Object> {
+ public TestStopUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ protected void startHalOperation() {
+
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ mUserStoppedCallback.onUserStopped();
+ callback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+
+ private static class TestStartUserClient extends StartUserClient<Object> {
+ public TestStartUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStartedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ protected void startHalOperation() {
+
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ mUserStartedCallback.onUserStarted(getTargetUserId());
+ callback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 576f9c2..557d07c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4037,16 +4037,16 @@
}
@Test
- public void testGetSetNetworkSlicing() throws Exception {
+ public void testGetSetEnterpriseNetworkPreference() throws Exception {
assertExpectException(SecurityException.class, null,
- () -> dpm.setNetworkSlicingEnabled(false));
+ () -> dpm.setEnterpriseNetworkPreferenceEnabled(false));
assertExpectException(SecurityException.class, null,
- () -> dpm.isNetworkSlicingEnabled());
+ () -> dpm.isEnterpriseNetworkPreferenceEnabled());
setupProfileOwner();
- dpm.setNetworkSlicingEnabled(false);
- assertThat(dpm.isNetworkSlicingEnabled()).isFalse();
+ dpm.setEnterpriseNetworkPreferenceEnabled(false);
+ assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isFalse();
// TODO(b/178655595)
// verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
// any(UserHandle.class),
@@ -4055,8 +4055,8 @@
// any(Runnable.class)
//);
- dpm.setNetworkSlicingEnabled(true);
- assertThat(dpm.isNetworkSlicingEnabled()).isTrue();
+ dpm.setEnterpriseNetworkPreferenceEnabled(true);
+ assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isTrue();
// TODO(b/178655595)
// verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
// any(UserHandle.class),
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index a7b32ac..68a6e60 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -96,7 +96,7 @@
case ACTION_JOB_STOPPED:
mTestJobStatus.running = false;
mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = params.getStopReason();
+ mTestJobStatus.stopReason = params.getLegacyStopReason();
break;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
index 3ea86f2..87881bf 100644
--- a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
+++ b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
@@ -47,7 +47,7 @@
int reason = params.getStopReason();
int event = TestEnvironment.EVENT_STOP_JOB;
Log.d(TAG, "stop reason: " + String.valueOf(reason));
- if (reason == JobParameters.REASON_PREEMPT) {
+ if (reason == JobParameters.STOP_REASON_PREEMPT) {
event = TestEnvironment.EVENT_PREEMPT_JOB;
Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
}
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index ecff409..5066240 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -47,12 +47,14 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.LocalServices;
import org.junit.After;
@@ -126,6 +128,10 @@
.setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
.setExtras(new Bundle())
.build();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ Boolean.toString(false),
+ true /* makeDefault*/);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index b09a3c3..fac5c1f 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -22,13 +22,17 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
@@ -38,9 +42,11 @@
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.Range;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
@@ -71,6 +77,14 @@
private static final String PACKAGE_3 = "pkg3";
private static final String CLASS_1 = "cls1";
private static final String CLASS_2 = "cls2";
+ private static final AppTargetEvent APP_TARGET_EVENT =
+ new AppTargetEvent.Builder(
+ new AppTarget.Builder(
+ new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)).build(),
+ AppTargetEvent.ACTION_LAUNCH)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
+ .build();
+ private static final IntentFilter INTENT_FILTER = IntentFilter.create("SEND", "text/plain");
@Mock private Context mContext;
@Mock private DataManager mDataManager;
@@ -102,17 +116,33 @@
when(mDataManager.getShareShortcuts(any(), anyInt())).thenReturn(mShareShortcuts);
when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1);
when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2);
+ when(mContext.createContextAsUser(any(), any())).thenReturn(mContext);
+ when(mContext.getSystemServiceName(AppPredictionManager.class)).thenReturn(
+ Context.APP_PREDICTION_SERVICE);
+ when(mContext.getSystemService(AppPredictionManager.class))
+ .thenReturn(new AppPredictionManager(mContext));
Bundle bundle = new Bundle();
- bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY,
- IntentFilter.create("SEND", "text/plain"));
+ bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, INTENT_FILTER);
AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
.setUiSurface(UI_SURFACE_SHARE)
.setPredictedTargetCount(NUM_PREDICTED_TARGETS)
.setExtras(bundle)
.build();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ Boolean.toString(true),
+ true /* makeDefault*/);
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+ }
+
+ @Test
+ public void testReportAppTargetEvent() {
+ mPredictor.reportAppTargetEvent(APP_TARGET_EVENT);
+
+ verify(mDataManager, times(1))
+ .reportShareTargetEvent(eq(APP_TARGET_EVENT), eq(INTENT_FILTER));
}
@Test
@@ -240,7 +270,7 @@
.setExtras(new Bundle())
.build();
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
mPredictor.predictTargets();
@@ -349,7 +379,7 @@
.setExtras(new Bundle())
.build();
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
AppTarget appTarget1 = new AppTarget.Builder(
new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.build();
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 029e9a3..1ab70e5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -48,10 +48,12 @@
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchManager;
import android.app.appsearch.IAppSearchResultCallback;
import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.SearchResultPage;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -159,7 +161,7 @@
case Context.DEVICE_POLICY_SERVICE:
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
- return new AppSearchManager(getTestContext(), mMockAppSearchManager);
+ return new AppSearchManager(this, mMockAppSearchManager);
case Context.ROLE_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
@@ -189,6 +191,12 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
+ return this;
+ }
+
+ @Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
// ignore.
@@ -196,12 +204,6 @@
}
@Override
- public Context createContextAsUser(UserHandle user, int flags) {
- when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
- return this;
- }
-
- @Override
public void unregisterReceiver(BroadcastReceiver receiver) {
// ignore.
}
@@ -238,6 +240,15 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ super.createContextAsUser(user, flags);
+ final ServiceContext ctx = spy(new ServiceContext());
+ when(ctx.getUser()).thenReturn(user);
+ when(ctx.getUserId()).thenReturn(user.getIdentifier());
+ return ctx;
+ }
+
+ @Override
public int getUserId() {
return UserHandle.USER_SYSTEM;
}
@@ -620,6 +631,11 @@
protected Map<String, List<PackageIdentifier>> mSchemasPackageAccessible =
new ArrayMap<>(1);
+ private Map<String, Map<String, GenericDocument>> mDocumentMap = new ArrayMap<>(1);
+
+ private String getKey(int userId, String databaseName) {
+ return new StringBuilder().append(userId).append("@").append(databaseName).toString();
+ }
@Override
public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles,
@@ -653,21 +669,77 @@
public void putDocuments(String packageName, String databaseName,
List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final List<GenericDocument> docs = new ArrayList<>(documentBundles.size());
+ for (Bundle bundle : documentBundles) {
+ docs.add(new GenericDocument(bundle));
+ }
+ final AppSearchBatchResult.Builder<String, Void> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ Map<String, GenericDocument> docMap = mDocumentMap.get(key);
+ for (GenericDocument doc : docs) {
+ builder.setSuccess(doc.getUri(), null);
+ if (docMap == null) {
+ docMap = new ArrayMap<>(1);
+ mDocumentMap.put(key, docMap);
+ }
+ docMap.put(doc.getUri(), doc);
+ }
+ callback.onResult(builder.build());
}
@Override
public void getDocuments(String packageName, String databaseName, String namespace,
List<String> uris, Map<String, List<String>> typePropertyPaths, int userId,
IAppSearchBatchResultCallback callback) throws RemoteException {
- ignore(callback);
+ final AppSearchBatchResult.Builder<String, Bundle> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ for (String uri : uris) {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ key + " not found when getting: " + uri);
+ }
+ } else {
+ final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+ for (String uri : uris) {
+ if (docs.containsKey(uri)) {
+ builder.setSuccess(uri, docs.get(uri).getBundle());
+ } else {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "shortcut not found: " + uri);
+ }
+ }
+ }
+ callback.onResult(builder.build());
}
@Override
public void query(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
+ return;
+ }
+ final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values());
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 0);
+ final ArrayList<Bundle> resultBundles = new ArrayList<>();
+ for (GenericDocument document : documents) {
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putBundle("document", document.getBundle());
+ resultBundle.putString("packageName", packageName);
+ resultBundle.putString("databaseName", databaseName);
+ resultBundle.putParcelableArrayList("matches", new ArrayList<>());
+ resultBundles.add(resultBundle);
+ }
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
}
@Override
@@ -679,7 +751,10 @@
@Override
public void getNextPage(long nextPageToken, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
}
@Override
@@ -698,14 +773,40 @@
public void removeByUri(String packageName, String databaseName, String namespace,
List<String> uris, int userId, IAppSearchBatchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final AppSearchBatchResult.Builder<String, Void> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ for (String uri : uris) {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "package " + key + " not found when removing " + uri);
+ }
+ } else {
+ final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+ for (String uri : uris) {
+ if (docs.containsKey(uri)) {
+ docs.remove(uri);
+ builder.setSuccess(uri, null);
+ } else {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "shortcut not found when removing " + uri);
+ }
+ }
+ }
+ callback.onResult(builder.build());
}
@Override
public void removeByQuery(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ return;
+ }
+ mDocumentMap.get(key).clear();
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
}
@Override
@@ -724,12 +825,12 @@
return null;
}
- private void ignore(IAppSearchResultCallback callback) throws RemoteException {
- callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ private void removeShortcuts() {
+ mDocumentMap.clear();
}
- private void ignore(IAppSearchBatchResultCallback callback) throws RemoteException {
- callback.onResult(new AppSearchBatchResult.Builder().build());
+ private void ignore(IAppSearchResultCallback callback) throws RemoteException {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
}
}
@@ -1146,6 +1247,9 @@
shutdownServices();
+ mMockAppSearchManager.removeShortcuts();
+ mMockAppSearchManager = null;
+
super.tearDown();
}
@@ -1891,6 +1995,11 @@
return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
}
+ protected void updatePackageShortcut(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ mService.updatePackageShortcutForTest(packageName, shortcutId, userId, cb);
+ }
+
protected void assertShortcutExists(String packageName, String shortcutId, int userId) {
assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
}
@@ -2086,6 +2195,10 @@
return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
}
+ protected void updateCallerShortcut(String shortcutId, Consumer<ShortcutInfo> cb) {
+ updatePackageShortcut(getCallingPackage(), shortcutId, getCallingUserId(), cb);
+ }
+
protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
final List<ShortcutInfo>[] ret = new List[1];
runWithCaller(launcher, userId, () -> {
@@ -2245,6 +2358,8 @@
deleteAllSavedFiles();
+ mMockAppSearchManager.removeShortcuts();
+
initService();
mService.applyRestore(payload, USER_0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 4d0beef..3f680e6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1385,6 +1385,7 @@
mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
+ Log.d("ShortcutManagerTest1", si.toString());
assertTrue(si.hasIconFile());
});
@@ -1702,8 +1703,8 @@
// Because setDynamicShortcuts will update the timestamps when ranks are changing,
// we explicitly set timestamps here.
- getCallerShortcut("s1").setTimestamp(5000);
- getCallerShortcut("s2").setTimestamp(1000);
+ updateCallerShortcut("s1", si -> si.setTimestamp(5000));
+ updateCallerShortcut("s2", si -> si.setTimestamp(1000));
setCaller(CALLING_PACKAGE_2);
final ShortcutInfo s2_2 = makeShortcut("s2");
@@ -1713,9 +1714,9 @@
makeComponent(ShortcutActivity.class));
assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
- getCallerShortcut("s2").setTimestamp(1500);
- getCallerShortcut("s3").setTimestamp(3000);
- getCallerShortcut("s4").setTimestamp(500);
+ updateCallerShortcut("s2", si -> si.setTimestamp(1500));
+ updateCallerShortcut("s3", si -> si.setTimestamp(3000));
+ updateCallerShortcut("s4", si -> si.setTimestamp(500));
setCaller(CALLING_PACKAGE_3);
final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
@@ -1723,7 +1724,7 @@
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
- getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
+ updateCallerShortcut("s3", si -> si.setTimestamp(START_TIME + 5000));
setCaller(LAUNCHER_1);
@@ -7686,7 +7687,7 @@
assertEquals("http://www/", si.getIntent().getData().toString());
assertEquals("foo/bar", si.getIntent().getType());
assertEquals(
- new ComponentName("abc", ".xyz"), si.getIntent().getComponent());
+ new ComponentName("abc", "abc.xyz"), si.getIntent().getComponent());
assertEquals(set("cat1", "cat2"), si.getIntent().getCategories());
assertEquals("value1", si.getIntent().getStringExtra("key1"));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 395b643..fc26611 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -58,6 +58,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
import javax.annotation.concurrent.GuardedBy;
@@ -158,6 +159,40 @@
fail("Didn't find a guest: " + list);
}
+ @Test
+ public void testCloneUser() throws Exception {
+ // Test that only one clone user can be created
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ UserInfo userInfo = createProfileForUser("Clone user1",
+ UserManager.USER_TYPE_PROFILE_CLONE,
+ primaryUserId);
+ assertThat(userInfo).isNotNull();
+ UserInfo userInfo2 = createProfileForUser("Clone user2",
+ UserManager.USER_TYPE_PROFILE_CLONE,
+ primaryUserId);
+ assertThat(userInfo2).isNull();
+
+ final Context userContext = mContext.createPackageContextAsUser("system", 0,
+ UserHandle.of(userInfo.id));
+ assertThat(userContext.getSystemService(
+ UserManager.class).sharesMediaWithParent()).isTrue();
+
+ List<UserInfo> list = mUserManager.getUsers();
+ List<UserInfo> cloneUsers = list.stream().filter(
+ user -> (user.id == userInfo.id && user.name.equals("Clone user1")
+ && user.isCloneProfile()))
+ .collect(Collectors.toList());
+ assertThat(cloneUsers.size()).isEqualTo(1);
+
+ // Verify clone user parent
+ assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
+ UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
+ assertThat(parentProfileInfo).isNotNull();
+ assertThat(primaryUserId).isEqualTo(parentProfileInfo.id);
+ removeUser(userInfo.id);
+ assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
+ }
+
@MediumTest
@Test
public void testAdd2Users() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index 5a100a2..a0e9d97 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -72,7 +72,6 @@
private TestCallback mTestCallback;
private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider;
- private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
@Before
public void setUp() {
@@ -84,13 +83,10 @@
};
mTestThreadingDomain = new TestThreadingDomain();
mTestCallback = new TestCallback(mTestThreadingDomain);
- mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
- stubbedProviderMetricsLogger, mTestThreadingDomain, "primary",
- mTimeZoneAvailabilityChecker);
+ stubbedProviderMetricsLogger, mTestThreadingDomain, "primary");
mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
- stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary",
- mTimeZoneAvailabilityChecker);
+ stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary");
}
@Test
@@ -1185,10 +1181,9 @@
* Creates the instance.
*/
TestLocationTimeZoneProvider(ProviderMetricsLogger providerMetricsLogger,
- ThreadingDomain threadingDomain, String providerName,
- TimeZoneIdValidator timeZoneIdValidator) {
+ ThreadingDomain threadingDomain, String providerName) {
super(providerMetricsLogger, threadingDomain, providerName,
- timeZoneIdValidator);
+ new FakeTimeZoneProviderEventPreProcessor());
}
public void setFailDuringInitialization(boolean failInitialization) {
@@ -1321,14 +1316,4 @@
mTestProviderState.commitLatest();
}
}
-
- private static final class FakeTimeZoneIdValidator
- implements LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return true;
- }
-
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..e75d05c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.timezonedetector.location;
+
+/**
+ * Fake implementation of {@link TimeZoneProviderEventPreProcessor} which assumes that all events
+ * are valid or always uncertain if {@link #enterUncertainMode()} was called.
+ */
+public final class FakeTimeZoneProviderEventPreProcessor
+ implements TimeZoneProviderEventPreProcessor {
+
+ private boolean mIsUncertain = false;
+
+ @Override
+ public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
+ if (mIsUncertain) {
+ return TimeZoneProviderEvent.createUncertainEvent();
+ }
+ return timeZoneProviderEvent;
+ }
+
+ public void enterUncertainMode() {
+ mIsUncertain = true;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index d13a04e..0edb559 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -52,10 +52,8 @@
import java.time.Duration;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -68,13 +66,13 @@
private TestThreadingDomain mTestThreadingDomain;
private TestProviderListener mProviderListener;
- private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
+ private FakeTimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
@Before
public void setUp() {
mTestThreadingDomain = new TestThreadingDomain();
mProviderListener = new TestProviderListener();
- mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
+ mTimeZoneProviderEventPreProcessor = new FakeTimeZoneProviderEventPreProcessor();
}
@Test
@@ -82,9 +80,10 @@
String providerName = "arbitrary";
RecordingProviderMetricsLogger providerMetricsLogger = new RecordingProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
- mTimeZoneAvailabilityChecker.validIds("Europe/London");
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
// initialize()
provider.initialize(mProviderListener);
@@ -174,8 +173,10 @@
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
AtomicReference<Bundle> resultReference = new AtomicReference<>();
@@ -193,10 +194,11 @@
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
- mTimeZoneAvailabilityChecker.validIds("Europe/London");
// initialize()
provider.initialize(mProviderListener);
@@ -234,14 +236,17 @@
}
@Test
- public void considerSuggestionWithInvalidTimeZoneIdsAsUncertain() {
+ public void entersUncertainState_whenEventHasUnsupportedZones() {
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
provider.initialize(mProviderListener);
+ mTimeZoneProviderEventPreProcessor.enterUncertainMode();
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
@@ -309,8 +314,9 @@
TestLocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
- @NonNull TimeZoneIdValidator timeZoneIdValidator) {
- super(providerMetricsLogger, threadingDomain, providerName, timeZoneIdValidator);
+ @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
+ super(providerMetricsLogger,
+ threadingDomain, providerName, timeZoneProviderEventPreProcessor);
}
@Override
@@ -367,20 +373,6 @@
}
}
- private static final class FakeTimeZoneIdValidator
- implements LocationTimeZoneProvider.TimeZoneIdValidator {
- private final Set<String> mValidTimeZoneIds = new HashSet<>();
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return mValidTimeZoneIds.contains(timeZoneId);
- }
-
- public void validIds(String... timeZoneIdss) {
- mValidTimeZoneIds.addAll(asList(timeZoneIdss));
- }
- }
-
private static class StubbedProviderMetricsLogger implements
LocationTimeZoneProvider.ProviderMetricsLogger {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
deleted file mode 100644
index 5561b2c..0000000
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
+++ /dev/null
@@ -1,54 +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.timezonedetector.location;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.TimeZone;
-
-@Presubmit
-public class ZoneInfoDbTimeZoneIdValidatorTest {
- private final LocationTimeZoneProvider.TimeZoneIdValidator mTzChecker =
- new ZoneInfoDbTimeZoneIdValidator();
-
- @Test
- public void timeZoneIdsFromZoneInfoDbAreValid() {
- for (String timeZone : TimeZone.getAvailableIDs()) {
- assertWithMessage("Time zone %s should be supported", timeZone)
- .that(mTzChecker.isValid(timeZone)).isTrue();
- }
- }
-
- @Test
- public void nonExistingZones_areNotSupported() {
- List<String> nonExistingTimeZones = Arrays.asList(
- "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30"
- );
-
- for (String timeZone : nonExistingTimeZones) {
- assertWithMessage(timeZone + " is not a valid time zone")
- .that(mTzChecker.isValid(timeZone))
- .isFalse();
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
new file mode 100644
index 0000000..173705be
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.timezonedetector.location;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Presubmit;
+import android.service.timezone.TimeZoneProviderSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.TimeZone;
+
+/** Tests for {@link ZoneInfoDbTimeZoneProviderEventPreProcessor}. */
+@Presubmit
+public class ZoneInfoDbTimeZoneProviderEventPreProcessorTest {
+
+ private static final long ARBITRARY_TIME_MILLIS = 11223344;
+
+ private final ZoneInfoDbTimeZoneProviderEventPreProcessor mPreProcessor =
+ new ZoneInfoDbTimeZoneProviderEventPreProcessor();
+
+ @Test
+ public void timeZoneIdsFromZoneInfoDbAreValid() {
+ for (String timeZone : TimeZone.getAvailableIDs()) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ assertWithMessage("Time zone %s should be supported", timeZone)
+ .that(mPreProcessor.preProcess(event)).isEqualTo(event);
+ }
+ }
+
+ @Test
+ public void eventWithNonExistingZones_areMappedToUncertainEvent() {
+ List<String> nonExistingTimeZones = Arrays.asList(
+ "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
+
+ for (String timeZone : nonExistingTimeZones) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+
+ assertWithMessage(timeZone + " is not a valid time zone")
+ .that(mPreProcessor.preProcess(event))
+ .isEqualTo(TimeZoneProviderEvent.createUncertainEvent());
+ }
+ }
+
+ private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
+ return TimeZoneProviderEvent.createSuggestionEvent(
+ new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(Arrays.asList(timeZoneIds))
+ .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
+ .build());
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index b54b696..a959a5e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -20,6 +20,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
@@ -37,9 +41,9 @@
private static final int EFFECT_DURATION = 20;
- private final Map<Long, VibrationEffect.Prebaked> mEnabledAlwaysOnEffects = new HashMap<>();
- private final List<VibrationEffect> mEffects = new ArrayList<>();
- private final List<Integer> mAmplitudes = new ArrayList<>();
+ private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
+ private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
+ private final List<Float> mAmplitudes = new ArrayList<>();
private final Handler mHandler;
private final FakeNativeWrapper mNativeWrapper;
@@ -57,85 +61,96 @@
public OnVibrationCompleteListener listener;
public boolean isInitialized;
+ @Override
public void init(int vibratorId, OnVibrationCompleteListener listener) {
isInitialized = true;
this.vibratorId = vibratorId;
this.listener = listener;
}
+ @Override
public boolean isAvailable() {
return mIsAvailable;
}
+ @Override
public void on(long milliseconds, long vibrationId) {
- VibrationEffect effect = VibrationEffect.createOneShot(
- milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
- mEffects.add(effect);
+ mEffectSegments.add(
+ new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) milliseconds));
applyLatency();
scheduleListener(milliseconds, vibrationId);
}
+ @Override
public void off() {
}
- public void setAmplitude(int amplitude) {
+ @Override
+ public void setAmplitude(float amplitude) {
mAmplitudes.add(amplitude);
applyLatency();
}
+ @Override
public int[] getSupportedEffects() {
return mSupportedEffects;
}
+ @Override
public int[] getSupportedPrimitives() {
return mSupportedPrimitives;
}
+ @Override
public float getResonantFrequency() {
return mResonantFrequency;
}
+ @Override
public float getQFactor() {
return mQFactor;
}
+ @Override
public long perform(long effect, long strength, long vibrationId) {
if (mSupportedEffects == null
|| Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
return 0;
}
- mEffects.add(new VibrationEffect.Prebaked((int) effect, false, (int) strength));
+ mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength));
applyLatency();
scheduleListener(EFFECT_DURATION, vibrationId);
return EFFECT_DURATION;
}
- public long compose(VibrationEffect.Composition.PrimitiveEffect[] effect,
- long vibrationId) {
- VibrationEffect.Composed composed = new VibrationEffect.Composed(Arrays.asList(effect));
- mEffects.add(composed);
- applyLatency();
+ @Override
+ public long compose(PrimitiveSegment[] effects, long vibrationId) {
long duration = 0;
- for (VibrationEffect.Composition.PrimitiveEffect e : effect) {
- duration += EFFECT_DURATION + e.delay;
+ for (PrimitiveSegment primitive : effects) {
+ duration += EFFECT_DURATION + primitive.getDelay();
+ mEffectSegments.add(primitive);
}
+ applyLatency();
scheduleListener(duration, vibrationId);
return duration;
}
+ @Override
public void setExternalControl(boolean enabled) {
}
+ @Override
public long getCapabilities() {
return mCapabilities;
}
+ @Override
public void alwaysOnEnable(long id, long effect, long strength) {
- VibrationEffect.Prebaked prebaked = new VibrationEffect.Prebaked((int) effect, false,
- (int) strength);
+ PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength);
mEnabledAlwaysOnEffects.put(id, prebaked);
}
+ @Override
public void alwaysOnDisable(long id) {
mEnabledAlwaysOnEffects.remove(id);
}
@@ -222,21 +237,21 @@
* Return the amplitudes set by this controller, including zeroes for each time the vibrator was
* turned off.
*/
- public List<Integer> getAmplitudes() {
+ public List<Float> getAmplitudes() {
return new ArrayList<>(mAmplitudes);
}
- /** Return list of {@link VibrationEffect} played by this controller, in order. */
- public List<VibrationEffect> getEffects() {
- return new ArrayList<>(mEffects);
+ /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */
+ public List<VibrationEffectSegment> getEffectSegments() {
+ return new ArrayList<>(mEffectSegments);
}
/**
- * Return the {@link VibrationEffect.Prebaked} effect enabled with given id, or {@code null} if
+ * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if
* missing or disabled.
*/
@Nullable
- public VibrationEffect.Prebaked getAlwaysOnEffect(int id) {
+ public PrebakedSegment getAlwaysOnEffect(int id) {
return mEnabledAlwaysOnEffects.get((long) id);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
index b6c11fe..59c0b0e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -26,7 +26,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
-import android.os.CombinedVibrationEffect;
import android.os.Handler;
import android.os.IExternalVibratorService;
import android.os.PowerManagerInternal;
@@ -35,6 +34,10 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
@@ -130,51 +133,13 @@
}
@Test
- public void scale_withCombined_resolvesAndScalesRecursively() {
+ public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() {
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_HIGH);
- VibrationEffect prebaked = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- VibrationEffect oneShot = VibrationEffect.createOneShot(10, 10);
+ PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK,
+ /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
- CombinedVibrationEffect.Mono monoScaled = mVibrationScaler.scale(
- CombinedVibrationEffect.createSynced(prebaked),
- VibrationAttributes.USAGE_NOTIFICATION);
- VibrationEffect.Prebaked prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect();
- assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-
- CombinedVibrationEffect.Stereo stereoScaled = mVibrationScaler.scale(
- CombinedVibrationEffect.startSynced()
- .addVibrator(1, prebaked)
- .addVibrator(2, oneShot)
- .combine(),
- VibrationAttributes.USAGE_NOTIFICATION);
- prebakedScaled = (VibrationEffect.Prebaked) stereoScaled.getEffects().get(1);
- assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- VibrationEffect.OneShot oneshotScaled =
- (VibrationEffect.OneShot) stereoScaled.getEffects().get(2);
- assertTrue(oneshotScaled.getAmplitude() > 0);
-
- CombinedVibrationEffect.Sequential sequentialScaled = mVibrationScaler.scale(
- CombinedVibrationEffect.startSequential()
- .addNext(CombinedVibrationEffect.createSynced(prebaked))
- .addNext(CombinedVibrationEffect.createSynced(oneShot))
- .combine(),
- VibrationAttributes.USAGE_NOTIFICATION);
- monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(0);
- prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect();
- assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(1);
- oneshotScaled = (VibrationEffect.OneShot) monoScaled.getEffect();
- assertTrue(oneshotScaled.getAmplitude() > 0);
- }
-
- @Test
- public void scale_withPrebaked_setsEffectStrengthBasedOnSettings() {
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
- VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-
- VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
+ PrebakedSegment scaled = mVibrationScaler.scale(
effect, VibrationAttributes.USAGE_NOTIFICATION);
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
@@ -196,25 +161,33 @@
}
@Test
- public void scale_withPrebakedAndFallback_resolvesAndScalesRecursively() {
+ public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() {
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_HIGH);
- VibrationEffect.OneShot fallback2 = (VibrationEffect.OneShot) VibrationEffect.createOneShot(
- 10, VibrationEffect.DEFAULT_AMPLITUDE);
- VibrationEffect.Prebaked fallback1 = new VibrationEffect.Prebaked(
- VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback2);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback1);
+ VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
- effect, VibrationAttributes.USAGE_NOTIFICATION);
- VibrationEffect.Prebaked scaledFallback1 =
- (VibrationEffect.Prebaked) scaled.getFallbackEffect();
- VibrationEffect.OneShot scaledFallback2 =
- (VibrationEffect.OneShot) scaledFallback1.getFallbackEffect();
+ PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertEquals(scaledFallback1.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertTrue(scaledFallback2.getAmplitude() > 0);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_LOW);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_OFF);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
+ // Unexpected intensity setting will be mapped to STRONG.
+ assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
}
@Test
@@ -224,20 +197,20 @@
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_LOW);
- VibrationEffect.OneShot oneShot = mVibrationScaler.scale(
+ StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
- VibrationAttributes.USAGE_RINGTONE);
- assertTrue(oneShot.getAmplitude() > 0);
+ VibrationAttributes.USAGE_RINGTONE));
+ assertTrue(resolved.getAmplitude() > 0);
- VibrationEffect.Waveform waveform = mVibrationScaler.scale(
+ resolved = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createWaveform(new long[]{10},
new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
- VibrationAttributes.USAGE_RINGTONE);
- assertTrue(waveform.getAmplitudes()[0] > 0);
+ VibrationAttributes.USAGE_RINGTONE));
+ assertTrue(resolved.getAmplitude() > 0);
}
@Test
- public void scale_withOneShotWaveform_scalesAmplitude() {
+ public void scale_withOneShotAndWaveform_scalesAmplitude() {
mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_HIGH);
@@ -248,21 +221,21 @@
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
Vibrator.VIBRATION_INTENSITY_MEDIUM);
- VibrationEffect.OneShot oneShot = mVibrationScaler.scale(
- VibrationEffect.createOneShot(100, 100), VibrationAttributes.USAGE_RINGTONE);
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), VibrationAttributes.USAGE_RINGTONE));
// Ringtone scales up.
- assertTrue(oneShot.getAmplitude() > 100);
+ assertTrue(scaled.getAmplitude() > 0.5);
- VibrationEffect.Waveform waveform = mVibrationScaler.scale(
- VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, -1),
- VibrationAttributes.USAGE_NOTIFICATION);
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ VibrationAttributes.USAGE_NOTIFICATION));
// Notification scales down.
- assertTrue(waveform.getAmplitudes()[0] < 100);
+ assertTrue(scaled.getAmplitude() < 0.5);
- oneShot = mVibrationScaler.scale(VibrationEffect.createOneShot(100, 100),
- VibrationAttributes.USAGE_TOUCH);
+ scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
+ VibrationAttributes.USAGE_TOUCH));
// Haptic feedback does not scale.
- assertEquals(100, oneShot.getAmplitude());
+ assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
}
@Test
@@ -280,18 +253,23 @@
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose();
- VibrationEffect.Composed scaled = mVibrationScaler.scale(composed,
- VibrationAttributes.USAGE_RINGTONE);
+ PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed,
+ VibrationAttributes.USAGE_RINGTONE));
// Ringtone scales up.
- assertTrue(scaled.getPrimitiveEffects().get(0).scale > 0.5f);
+ assertTrue(scaled.getScale() > 0.5f);
- scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_NOTIFICATION);
+ scaled = getFirstSegment(mVibrationScaler.scale(composed,
+ VibrationAttributes.USAGE_NOTIFICATION));
// Notification scales down.
- assertTrue(scaled.getPrimitiveEffects().get(0).scale < 0.5f);
+ assertTrue(scaled.getScale() < 0.5f);
- scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH);
+ scaled = getFirstSegment(mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH));
// Haptic feedback does not scale.
- assertEquals(0.5, scaled.getPrimitiveEffects().get(0).scale, 1e-5);
+ assertEquals(0.5, scaled.getScale(), 1e-5);
+ }
+
+ private <T extends VibrationEffectSegment> T getFirstSegment(VibrationEffect.Composed effect) {
+ return (T) effect.getSegments().get(0);
}
private void setUserSetting(String settingName, int value) {
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 7d5eec0..739a1a3 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -40,6 +40,10 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
@@ -61,6 +65,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Tests for {@link VibrationThread}.
@@ -142,8 +147,8 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
@@ -161,7 +166,7 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@@ -183,8 +188,9 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(15)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
- assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+ assertEquals(expectedAmplitudes(1, 2, 3),
+ mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
@@ -213,12 +219,12 @@
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
- List<Integer> playedAmplitudes = fakeVibrator.getAmplitudes();
- assertFalse(fakeVibrator.getEffects().isEmpty());
+ List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
+ assertFalse(fakeVibrator.getEffectSegments().isEmpty());
assertFalse(playedAmplitudes.isEmpty());
for (int i = 0; i < playedAmplitudes.size(); i++) {
- assertEquals(amplitudes[i % amplitudes.length], playedAmplitudes.get(i).intValue());
+ assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5);
}
}
@@ -292,7 +298,7 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
}
@Test
@@ -302,9 +308,10 @@
long vibrationId = 1;
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_STRENGTH_STRONG, fallback);
- VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+ Vibration vibration = createVibration(vibrationId, CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
+ vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+ VibrationThread thread = startThreadAndDispatcher(vibration);
waitForCompletion(thread);
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
@@ -314,8 +321,8 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
@@ -331,7 +338,7 @@
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
eq(Vibration.Status.IGNORED_UNSUPPORTED));
- assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
}
@Test
@@ -351,7 +358,10 @@
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
- assertEquals(Arrays.asList(effect), mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ assertEquals(Arrays.asList(
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
}
@Test
@@ -368,7 +378,7 @@
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
eq(Vibration.Status.IGNORED_UNSUPPORTED));
- assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
}
@Test
@@ -428,7 +438,7 @@
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
- mVibratorProviders.get(VIBRATOR_ID).getEffects());
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
}
@Test
@@ -454,10 +464,10 @@
assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating());
- VibrationEffect expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
- assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffects());
+ VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffectSegments());
}
@Test
@@ -495,12 +505,16 @@
assertFalse(thread.getVibrators().get(4).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(2).getAmplitudes());
- assertEquals(Arrays.asList(expectedOneShot(20)), mVibratorProviders.get(3).getEffects());
- assertEquals(Arrays.asList(1, 2), mVibratorProviders.get(3).getAmplitudes());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(4).getEffects());
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expectedOneShot(10)),
+ mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(20)),
+ mVibratorProviders.get(3).getEffectSegments());
+ assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
+ assertEquals(Arrays.asList(
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ mVibratorProviders.get(4).getEffectSegments());
}
@Test
@@ -540,11 +554,14 @@
assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating());
- assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+ assertEquals(Arrays.asList(expectedOneShot(10)),
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(Arrays.asList(
+ expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+ mVibratorProviders.get(2).getEffectSegments());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
- mVibratorProviders.get(3).getEffects());
+ mVibratorProviders.get(3).getEffectSegments());
}
@Test
@@ -563,8 +580,9 @@
CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(composed);
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
- assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffects().isEmpty()
- && !mVibratorProviders.get(2).getEffects().isEmpty(), thread, TEST_TIMEOUT_MILLIS));
+ assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffectSegments().isEmpty()
+ && !mVibratorProviders.get(2).getEffectSegments().isEmpty(), thread,
+ TEST_TIMEOUT_MILLIS));
thread.syncedVibrationComplete();
waitForCompletion(thread);
@@ -574,8 +592,10 @@
verify(mThreadCallbacks, never()).cancelSyncedVibration();
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+ VibrationEffectSegment expected = expectedPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
}
@Test
@@ -634,10 +654,12 @@
verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId));
verify(mThreadCallbacks, never()).cancelSyncedVibration();
- assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(expectedOneShot(5)), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(200), mVibratorProviders.get(2).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(10)),
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(5)),
+ mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
}
@Test
@@ -704,12 +726,15 @@
assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating());
- assertEquals(Arrays.asList(expectedOneShot(25)), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(expectedOneShot(80)), mVibratorProviders.get(2).getEffects());
- assertEquals(Arrays.asList(expectedOneShot(60)), mVibratorProviders.get(3).getEffects());
- assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
- assertEquals(Arrays.asList(4, 5), mVibratorProviders.get(2).getAmplitudes());
- assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(25)),
+ mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expectedOneShot(80)),
+ mVibratorProviders.get(2).getEffectSegments());
+ assertEquals(Arrays.asList(expectedOneShot(60)),
+ mVibratorProviders.get(3).getEffectSegments());
+ assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
+ assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
}
@LargeTest
@@ -826,7 +851,7 @@
verify(mVibrationToken).linkToDeath(same(thread), eq(0));
verify(mVibrationToken).unlinkToDeath(same(thread), eq(0));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
- assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+ assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
}
@@ -843,12 +868,16 @@
private VibrationThread startThreadAndDispatcher(long vibrationId,
CombinedVibrationEffect effect) {
- VibrationThread thread = new VibrationThread(createVibration(vibrationId, effect),
- createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks);
+ return startThreadAndDispatcher(createVibration(vibrationId, effect));
+ }
+
+ private VibrationThread startThreadAndDispatcher(Vibration vib) {
+ VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock,
+ mIBatteryStatsMock, mThreadCallbacks);
doAnswer(answer -> {
thread.vibratorComplete(answer.getArgument(0));
return null;
- }).when(mControllerCallbacks).onComplete(anyInt(), eq(vibrationId));
+ }).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id));
mTestLooper.startAutoDispatch();
thread.start();
return thread;
@@ -891,12 +920,21 @@
return array;
}
- private VibrationEffect expectedOneShot(long millis) {
- return VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE);
+ private VibrationEffectSegment expectedOneShot(long millis) {
+ return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) millis);
}
- private VibrationEffect expectedPrebaked(int effectId) {
- return new VibrationEffect.Prebaked(effectId, false,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ private VibrationEffectSegment expectedPrebaked(int effectId) {
+ return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ }
+
+ private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) {
+ return new PrimitiveSegment(primitiveId, scale, delay);
+ }
+
+ private List<Float> expectedAmplitudes(int... amplitudes) {
+ return Arrays.stream(amplitudes)
+ .mapToObj(amplitude -> amplitude / 255f)
+ .collect(Collectors.toList());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
index bad3e4c..0ba3a21b 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -39,6 +39,8 @@
import android.os.IVibratorStateListener;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -49,7 +51,6 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -161,9 +162,9 @@
@Test
public void updateAlwaysOn_withCapability_enablesAlwaysOnEffect() {
mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- createController().updateAlwaysOn(1, effect);
+ PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ createController().updateAlwaysOn(1, prebaked);
verify(mNativeWrapperMock).alwaysOnEnable(
eq(1L), eq((long) VibrationEffect.EFFECT_CLICK),
@@ -179,9 +180,9 @@
@Test
public void updateAlwaysOn_withoutCapability_ignoresEffect() {
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- createController().updateAlwaysOn(1, effect);
+ PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ createController().updateAlwaysOn(1, prebaked);
verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong());
verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong());
@@ -201,9 +202,9 @@
when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
VibratorController controller = createController();
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- assertEquals(10L, controller.on(effect, 11));
+ PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+ VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertEquals(10L, controller.on(prebaked, 11));
assertTrue(controller.isVibrating());
verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
@@ -216,24 +217,13 @@
when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
VibratorController controller = createController();
- VibrationEffect.Composed effect = (VibrationEffect.Composed)
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .compose();
- assertEquals(15L, controller.on(effect, 12));
-
- ArgumentCaptor<VibrationEffect.Composition.PrimitiveEffect[]> primitivesCaptor =
- ArgumentCaptor.forClass(VibrationEffect.Composition.PrimitiveEffect[].class);
+ PrimitiveSegment[] primitives = new PrimitiveSegment[]{
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+ };
+ assertEquals(15L, controller.on(primitives, 12));
assertTrue(controller.isVibrating());
- verify(mNativeWrapperMock).compose(primitivesCaptor.capture(), eq(12L));
-
- // Check all primitive effect fields are passed down to the HAL.
- assertEquals(1, primitivesCaptor.getValue().length);
- VibrationEffect.Composition.PrimitiveEffect primitive = primitivesCaptor.getValue()[0];
- assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.id);
- assertEquals(0.5f, primitive.scale, /* delta= */ 1e-2);
- assertEquals(10, primitive.delay);
+ verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
}
@Test
@@ -286,4 +276,8 @@
private void mockVibratorCapabilities(int capabilities) {
when(mNativeWrapperMock.getCapabilities()).thenReturn((long) capabilities);
}
+
+ private PrebakedSegment createPrebaked(int effectId, int effectStrength) {
+ return new PrebakedSegment(effectId, /* shouldFallback= */ false, effectStrength);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ce6639c..12ced38 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -65,6 +65,8 @@
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.InputDevice;
@@ -364,13 +366,13 @@
assertTrue(createSystemReadyService().setAlwaysOnEffect(
UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
- VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
+ PrebakedSegment expected = new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
// Only vibrators 1 and 3 have always-on capabilities.
- assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedEffect);
+ assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected);
assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1));
- assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expectedEffect);
+ assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expected);
}
@Test
@@ -388,10 +390,10 @@
assertTrue(createSystemReadyService().setAlwaysOnEffect(
UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
- VibrationEffect.Prebaked expectedClick = new VibrationEffect.Prebaked(
+ PrebakedSegment expectedClick = new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
- VibrationEffect.Prebaked expectedTick = new VibrationEffect.Prebaked(
+ PrebakedSegment expectedTick = new PrebakedSegment(
VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
// Enables click on vibrator 1 and tick on vibrator 2 only.
@@ -487,8 +489,9 @@
vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
- assertEquals(2, mVibratorProviders.get(1).getEffects().size());
- assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(2, mVibratorProviders.get(1).getEffectSegments().size());
+ assertEquals(Arrays.asList(10 / 255f, 100 / 255f),
+ mVibratorProviders.get(1).getAmplitudes());
}
@Test
@@ -500,19 +503,19 @@
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3,
service, TEST_TIMEOUT_MILLIS));
- assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
+ assertEquals(Arrays.asList(2 / 255f, 3 / 255f, 4 / 255f), fakeVibrator.getAmplitudes());
}
@Test
@@ -579,7 +582,7 @@
verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
// VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
+ assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(),
service, /* timeout= */ 50));
}
@@ -640,8 +643,11 @@
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
verify(mNativeWrapperMock).triggerSynced(anyLong());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
- assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+
+ PrimitiveSegment expected = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+ assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
}
@Test
@@ -665,7 +671,7 @@
.compose())
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -689,7 +695,7 @@
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock, never()).prepareSynced(any());
@@ -709,7 +715,7 @@
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -730,7 +736,7 @@
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -758,40 +764,38 @@
vibrate(service, CombinedVibrationEffect.startSynced()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.combine(), ALARM_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, CombinedVibrationEffect.startSequential()
.addNext(1, VibrationEffect.createOneShot(20, 100))
.combine(), NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose(), HAPTIC_FEEDBACK_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 4,
service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
- assertEquals(3, fakeVibrator.getEffects().size());
+ assertEquals(4, fakeVibrator.getEffectSegments().size());
assertEquals(1, fakeVibrator.getAmplitudes().size());
// Alarm vibration is always VIBRATION_INTENSITY_HIGH.
- VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
- VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertEquals(expected, fakeVibrator.getEffects().get(0));
+ PrebakedSegment expected = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertEquals(expected, fakeVibrator.getEffectSegments().get(0));
// Notification vibrations will be scaled with SCALE_VERY_HIGH.
- assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
+ assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0));
// Haptic feedback vibrations will be scaled with SCALE_LOW.
- VibrationEffect.Composed played =
- (VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
- assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
- assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
+ assertTrue(0.5 < ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale());
+ assertTrue(0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(3)).getScale());
// Ring vibrations have intensity OFF and are not played.
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 5462f47..ff88174 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -1688,8 +1688,8 @@
@Override
public boolean matches(VibrationEffect actual) {
- if (actual instanceof VibrationEffect.Waveform &&
- ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+ if (actual instanceof VibrationEffect.Composed
+ && ((VibrationEffect.Composed) actual).getRepeatIndex() == mRepeatIndex) {
return true;
}
// All non-waveform effects are essentially one shots.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index b9ffd65..a37d5c8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -31,9 +31,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.media.session.MediaSession;
import android.os.Build;
-import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
@@ -73,7 +71,6 @@
private NotificationRecord mRecordMinCallNonInterruptive;
private NotificationRecord mRecordMinCall;
private NotificationRecord mRecordHighCall;
- private NotificationRecord mRecordDefaultMedia;
private NotificationRecord mRecordEmail;
private NotificationRecord mRecordInlineReply;
private NotificationRecord mRecordSms;
@@ -139,15 +136,6 @@
new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification n3 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
- .setStyle(new Notification.MediaStyle()
- .setMediaSession(new MediaSession.Token(Process.myUid(), null)))
- .build();
- mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
- "", 1499), getDefaultChannel());
- mRecordDefaultMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
-
Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setStyle(new Notification.MessagingStyle("sender!")).build();
mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
@@ -218,7 +206,7 @@
.setStyle(new Notification.MediaStyle())
.build();
mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification(
- pkg2, pkg2, 1, "cheater", uid2, uid2, n12, new UserHandle(userId),
+ pkg2, pkg2, 1, "media", uid2, uid2, n12, new UserHandle(userId),
"", 9258), getDefaultChannel());
mNoMediaSessionMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
@@ -247,7 +235,6 @@
final List<NotificationRecord> expected = new ArrayList<>();
expected.add(mRecordColorizedCall);
expected.add(mRecordColorized);
- expected.add(mRecordDefaultMedia);
expected.add(mRecordHighCall);
expected.add(mRecordInlineReply);
if (mRecordSms != null) {
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 e37b82f..aa9feeaa 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -4910,6 +4910,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4933,6 +4934,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4952,6 +4954,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4974,11 +4977,12 @@
}
@Test
- public void testToastRateLimiterCanPreventsShowCallForCustomToast() throws Exception {
+ public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4995,12 +4999,36 @@
}
@Test
+ public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(false); // rate limit reached
+ // Avoids rate limiting.
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ .thenReturn(false);
+
+ setAppInForegroundForToasts(mUid, true);
+
+ Binder token = new Binder();
+ ITransientNotification callback = mock(ITransientNotification.class);
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ nmService.enqueueToast(testPackage, token, callback, 2000, 0);
+ verify(callback).show(any());
+ }
+
+ @Test
public void testCustomToastPostedWhileInForeground_blockedIfAppGoesToBackground()
throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5034,6 +5062,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5053,6 +5082,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5072,6 +5102,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5095,11 +5126,12 @@
}
@Test
- public void testToastRateLimiterCanPreventsShowCallForTextToast() throws Exception {
+ public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5114,12 +5146,32 @@
}
@Test
+ public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ .thenReturn(false);
+
+ Binder token = new Binder();
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+ verify(mStatusBar).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+ }
+
+ @Test
public void backgroundSystemCustomToast_callsSetProcessImportantAsForegroundForToast() throws
Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = true;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5145,6 +5197,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5166,6 +5219,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5186,6 +5240,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5203,6 +5258,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5224,6 +5280,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5247,6 +5304,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = true;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5270,6 +5328,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5305,6 +5364,13 @@
.thenReturn(isWithinQuota);
}
+ private void setIfPackageHasPermissionToAvoidToastRateLimiting(
+ String pkg, boolean hasPermission) throws Exception {
+ when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
+ pkg, UserHandle.getUserId(mUid)))
+ .thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
+ }
+
@Test
public void testOnPanelRevealedAndHidden() {
int items = 5;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
index 3b15376..1116204 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
@@ -316,9 +316,10 @@
mScheduleCalendar.setSchedule(mScheduleInfo);
mScheduleInfo.nextAlarm = 2000;
+ // next alarm updated to 3000 (alarm for 2000 was changed to 3000)
mScheduleCalendar.maybeSetNextAlarm(1000, 3000);
- assertEquals(2000, mScheduleInfo.nextAlarm);
+ assertEquals(3000, mScheduleInfo.nextAlarm);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 5a527a2..7446e9e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -279,17 +279,17 @@
conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
assertEquals(Condition.STATE_TRUE, condition.state);
- // in schedule, update with later alarm time, should be in dnd
+ // in schedule, update with nextAlarm = later alarm time (1000), should be in dnd
condition = mService.evaluateSubscriptionLocked(
conditionId, cal, now.getTimeInMillis() + 250, now.getTimeInMillis() + 1000);
assertEquals(Condition.STATE_TRUE, condition.state);
- // at earliest alarm fire time, should exit dnd
- assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500));
+ // at next alarm fire time (1000), should exit dnd
+ assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
- cal.shouldExitForAlarm(now.getTimeInMillis() + 500));
+ cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
condition = mService.evaluateSubscriptionLocked(
- conditionId, cal, now.getTimeInMillis() + 500, 0);
+ conditionId, cal, now.getTimeInMillis() + 1000, 0);
assertEquals(Condition.STATE_FALSE, condition.state);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 002859e..7c9ff79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -38,6 +38,7 @@
import android.app.ActivityOptions.SourceInfo;
import android.app.WaitResult;
import android.content.Intent;
+import android.os.IBinder;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
@@ -402,6 +403,26 @@
}
@Test
+ public void testConsecutiveLaunchNewTask() {
+ final IBinder launchCookie = mock(IBinder.class);
+ mTrampolineActivity.noDisplay = true;
+ mTrampolineActivity.mLaunchCookie = launchCookie;
+ onActivityLaunched(mTrampolineActivity);
+ final ActivityRecord activityOnNewTask = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+ mActivityMetricsLogger.notifyActivityLaunching(activityOnNewTask.intent,
+ mTrampolineActivity /* caller */);
+ notifyActivityLaunched(START_SUCCESS, activityOnNewTask);
+
+ transitToDrawnAndVerifyOnLaunchFinished(activityOnNewTask);
+ assertWithMessage("Trampoline's cookie must be transferred").that(
+ mTrampolineActivity.mLaunchCookie).isNull();
+ assertWithMessage("The last launch task has the transferred cookie").that(
+ activityOnNewTask.mLaunchCookie).isEqualTo(launchCookie);
+ }
+
+ @Test
public void testConsecutiveLaunchOnDifferentDisplay() {
onActivityLaunched(mTopActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 32e5200..adf8fa4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -46,6 +46,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -1267,6 +1268,42 @@
is(Configuration.ORIENTATION_PORTRAIT));
}
+ @Test
+ public void testHybridRotationAnimation() {
+ final DisplayContent displayContent = mDefaultDisplay;
+ final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
+ final WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ final WindowState[] windows = { statusBar, navBar, app };
+ makeWindowVisible(windows);
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
+ displayPolicy.addWindowLw(navBar, navBar.mAttrs);
+ final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
+ displayContent.getRotation());
+ spyOn(rotationAnim);
+ // Assume that the display rotation is changed so it is frozen in preparation for animation.
+ doReturn(true).when(rotationAnim).hasScreenshot();
+ mWm.mDisplayFrozen = true;
+ displayContent.setRotationAnimation(rotationAnim);
+ // The fade rotation animation also starts to hide some non-app windows.
+ assertNotNull(displayContent.getFadeRotationAnimationController());
+ assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
+
+ for (WindowState w : windows) {
+ w.setOrientationChanging(true);
+ }
+ // The display only waits for the app window to unfreeze.
+ assertFalse(displayContent.waitForUnfreeze(statusBar));
+ assertFalse(displayContent.waitForUnfreeze(navBar));
+ assertTrue(displayContent.waitForUnfreeze(app));
+ // If all windows animated by fade rotation animation have done the orientation change,
+ // the animation controller should be cleared.
+ statusBar.setOrientationChanging(false);
+ navBar.setOrientationChanging(false);
+ assertNull(displayContent.getFadeRotationAnimationController());
+ }
+
@UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR })
@Test
public void testApplyTopFixedRotationTransform() {
@@ -1275,6 +1312,7 @@
doReturn(false).when(displayPolicy).navigationBarCanMove();
displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+ makeWindowVisible(mStatusBarWindow, mNavBarWindow);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
@@ -1297,7 +1335,7 @@
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
- assertNotNull(mDisplayContent.getFixedRotationAnimationController());
+ assertNotNull(mDisplayContent.getFadeRotationAnimationController());
assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
ANIMATION_TYPE_FIXED_TRANSFORM));
assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
@@ -1381,7 +1419,7 @@
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
- assertNull(mDisplayContent.getFixedRotationAnimationController());
+ assertNull(mDisplayContent.getFadeRotationAnimationController());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 7a4ad74..f97e794 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -556,11 +556,11 @@
}
@Test
- public void testNotAttachNavigationBar_controlledByFixedRotationAnimation() {
+ public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
setupForShouldAttachNavBarDuringTransition();
- FixedRotationAnimationController mockController =
- mock(FixedRotationAnimationController.class);
- doReturn(mockController).when(mDefaultDisplay).getFixedRotationAnimationController();
+ FadeRotationAnimationController mockController =
+ mock(FadeRotationAnimationController.class);
+ doReturn(mockController).when(mDefaultDisplay).getFadeRotationAnimationController();
final ActivityRecord homeActivity = createHomeActivity();
initializeRecentsAnimationController(mController, homeActivity);
assertFalse(mController.isNavigationBarAttachedToApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 956c277..37da529 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -578,10 +578,10 @@
}
@Test
- public void testNonAppTarget_notSendNavBar_controlledByFixedRotation() throws Exception {
- final FixedRotationAnimationController mockController =
- mock(FixedRotationAnimationController.class);
- doReturn(mockController).when(mDisplayContent).getFixedRotationAnimationController();
+ public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
+ final FadeRotationAnimationController mockController =
+ mock(FadeRotationAnimationController.class);
+ doReturn(mockController).when(mDisplayContent).getFadeRotationAnimationController();
final int transit = TRANSIT_OLD_TASK_OPEN;
setupForNonAppTargetNavBar(transit, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index e2585e5..16e0d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_IME;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -250,4 +251,29 @@
verify(selectFunc).apply(token2.windowType, options);
}
+
+ /**
+ * Test that {@link WindowToken#setInsetsFrozen(boolean)} will set the frozen insets
+ * states for its children windows and by default it shouldn't let IME window setting
+ * the frozen insets state even the window of the window token is the IME layering target.
+ */
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testSetInsetsFrozen_notAffectImeWindowState() {
+ // Pre-condition: make the IME window be controlled by IME insets provider.
+ mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow(
+ mDisplayContent.mInputMethodWindow, null, null);
+
+ // Simulate an app window to be the IME layering target, assume the app window has no
+ // frozen insets state by default.
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ mDisplayContent.setImeLayeringTarget(app);
+ assertNull(app.getFrozenInsetsState());
+ assertTrue(app.isImeLayeringTarget());
+
+ // Verify invoking setInsetsFrozen shouldn't let IME window setting the frozen insets state.
+ app.mToken.setInsetsFrozen(true);
+ assertNotNull(app.getFrozenInsetsState());
+ assertNull(mDisplayContent.mInputMethodWindow.getFrozenInsetsState());
+ }
}
diff --git a/services/translation/java/com/android/server/translation/RemoteTranslationService.java b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
index 0c7e617..82cb728 100644
--- a/services/translation/java/com/android/server/translation/RemoteTranslationService.java
+++ b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
@@ -20,9 +20,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.os.ResultReceiver;
import android.service.translation.ITranslationService;
import android.service.translation.TranslationService;
import android.util.Slog;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import com.android.internal.infra.AbstractRemoteService;
@@ -80,8 +82,14 @@
return mIdleUnbindTimeoutMs;
}
- public void onSessionCreated(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
- run((s) -> s.onCreateTranslationSession(sourceSpec, destSpec, sessionId, resultReceiver));
+ public void onSessionCreated(@NonNull TranslationContext translationContext, int sessionId,
+ IResultReceiver resultReceiver) {
+ run((s) -> s.onCreateTranslationSession(translationContext, sessionId, resultReceiver));
+ }
+
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ run((s) -> s.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, resultReceiver));
}
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 72e1e33..6203ae9 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -34,6 +34,7 @@
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.translation.ITranslationManager;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
@@ -142,29 +143,33 @@
}
final class TranslationManagerServiceStub extends ITranslationManager.Stub {
+
@Override
- public void getSupportedLocales(IResultReceiver receiver, int userId)
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ ResultReceiver receiver, int userId)
throws RemoteException {
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
- || isCalledByServiceAppLocked(userId, "getSupportedLocales"))) {
- service.getSupportedLocalesLocked(receiver);
+ || isCalledByServiceAppLocked(userId, "getTranslationCapabilities"))) {
+ service.onTranslationCapabilitiesRequestLocked(sourceFormat, targetFormat,
+ receiver);
} else {
- Slog.v(TAG, "getSupportedLocales(): no service for " + userId);
+ Slog.v(TAG, "onGetTranslationCapabilitiesLocked(): no service for " + userId);
receiver.send(STATUS_SYNC_CALL_FAIL, null);
}
}
}
@Override
- public void onSessionCreated(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ public void onSessionCreated(TranslationContext translationContext,
int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId, "onSessionCreated"))) {
- service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver);
+ service.onSessionCreatedLocked(translationContext, sessionId, receiver);
} else {
Slog.v(TAG, "onSessionCreated(): no service for " + userId);
receiver.send(STATUS_SYNC_CALL_FAIL, null);
@@ -174,7 +179,7 @@
@Override
public void updateUiTranslationStateByTaskId(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
int taskId, int userId) {
// deprecated
enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
@@ -183,7 +188,7 @@
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId,
"updateUiTranslationStateByTaskId"))) {
- service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+ service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
taskId);
}
}
@@ -191,14 +196,14 @@
@Override
public void updateUiTranslationState(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
IBinder token, int taskId, int userId) {
enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
- service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+ service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
token, taskId);
}
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 1ca07cb..ee5ec47 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.translation;
-import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE;
import static android.view.translation.UiTranslationManager.EXTRA_STATE;
import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE;
@@ -31,23 +30,23 @@
import android.os.IRemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.translation.TranslationServiceInfo;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.inputmethod.InputMethodInfo;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
-import com.android.internal.util.SyncResultReceiver;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
-import java.util.ArrayList;
import java.util.List;
final class TranslationManagerServiceImpl extends
@@ -122,28 +121,28 @@
}
@GuardedBy("mLock")
- void getSupportedLocalesLocked(@NonNull IResultReceiver resultReceiver) {
- // TODO: implement this
- try {
- resultReceiver.send(STATUS_SYNC_CALL_SUCCESS,
- SyncResultReceiver.bundleFor(new ArrayList<>()));
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException returning supported locales: " + e);
+ void onTranslationCapabilitiesRequestLocked(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int destFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
+ if (remoteService != null) {
+ remoteService.onTranslationCapabilitiesRequest(sourceFormat, destFormat,
+ resultReceiver);
}
}
@GuardedBy("mLock")
- void onSessionCreatedLocked(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ void onSessionCreatedLocked(@NonNull TranslationContext translationContext, int sessionId,
+ IResultReceiver resultReceiver) {
final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
if (remoteService != null) {
- remoteService.onSessionCreated(sourceSpec, destSpec, sessionId, resultReceiver);
+ remoteService.onSessionCreated(translationContext, sessionId, resultReceiver);
}
}
@GuardedBy("mLock")
public void updateUiTranslationStateLocked(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
int taskId) {
// deprecated
final ActivityTokens taskTopActivityTokens =
@@ -152,13 +151,13 @@
Slog.w(TAG, "Unknown activity to query for update translation state.");
return;
}
- updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
- viewIds);
+ updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+ targetSpec, viewIds);
}
@GuardedBy("mLock")
public void updateUiTranslationStateLocked(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
IBinder token, int taskId) {
// Get top activity for a given task id
final ActivityTokens taskTopActivityTokens =
@@ -169,20 +168,20 @@
+ "translation state for token=" + token + " taskId=" + taskId);
return;
}
- updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
- viewIds);
+ updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+ targetSpec, viewIds);
}
private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens,
- @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec,
+ @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec targetSpec,
List<AutofillId> viewIds) {
try {
tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state,
- sourceSpec, destSpec, viewIds);
+ sourceSpec, targetSpec, viewIds);
} catch (RemoteException e) {
Slog.w(TAG, "Update UiTranslationState fail: " + e);
}
- invokeCallbacks(state, sourceSpec, destSpec);
+ invokeCallbacks(state, sourceSpec, targetSpec);
}
private void invokeCallbacks(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 05573f1..df3ca99 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -27,6 +27,7 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
+import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.content.BroadcastReceiver;
@@ -35,7 +36,9 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.os.Bundle;
import android.os.Handler;
@@ -167,12 +170,8 @@
mSessionComponentName = new ComponentName(service.getPackageName(),
mInfo.getSessionService());
final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService();
- if (hotwordDetectionServiceName != null) {
- mHotwordDetectionComponentName = new ComponentName(service.getPackageName(),
- hotwordDetectionServiceName);
- } else {
- mHotwordDetectionComponentName = null;
- }
+ mHotwordDetectionComponentName = hotwordDetectionServiceName != null
+ ? new ComponentName(service.getPackageName(), hotwordDetectionServiceName) : null;
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
IntentFilter filter = new IntentFilter();
@@ -456,8 +455,22 @@
}
boolean isIsolatedProcessLocked(ComponentName componentName) {
- // TODO : Need to make sure this component exists and is :isolated.
- return true;
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, mUser);
+ if (serviceInfo != null) {
+ return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+ && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
+ }
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "isIsolatedProcess RemoteException : " + e);
+ }
+ }
+ return false;
}
public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -476,6 +489,7 @@
pw.println(" Service info:");
mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), " ");
pw.print(" Recognition service="); pw.println(mInfo.getRecognitionService());
+ pw.print(" Hotword detection service="); pw.println(mInfo.getHotwordDetectionService());
pw.print(" Settings activity="); pw.println(mInfo.getSettingsActivity());
pw.print(" Supports assist="); pw.println(mInfo.getSupportsAssist());
pw.print(" Supports launch from keyguard=");
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2a5ddfd..4e64838 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -138,6 +138,27 @@
public static final int STATE_SIMULATED_RINGING = 13;
/**
+ * @hide
+ */
+ @IntDef(prefix = { "STATE_" },
+ value = {
+ STATE_NEW,
+ STATE_DIALING,
+ STATE_RINGING,
+ STATE_HOLDING,
+ STATE_ACTIVE,
+ STATE_DISCONNECTED,
+ STATE_SELECT_PHONE_ACCOUNT,
+ STATE_CONNECTING,
+ STATE_DISCONNECTING,
+ STATE_PULLING_CALL,
+ STATE_AUDIO_PROCESSING,
+ STATE_SIMULATED_RINGING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CallState {};
+
+ /**
* The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call
* extras. Used to pass the phone accounts to display on the front end to the user in order to
* select phone accounts to (for example) place a call.
@@ -674,6 +695,7 @@
// Next PROPERTY value: 0x00004000
//******************************************************************************************
+ private final @CallState int mState;
private final String mTelecomCallId;
private final Uri mHandle;
private final int mHandlePresentation;
@@ -868,6 +890,13 @@
return builder.toString();
}
+ /**
+ * @return the state of the {@link Call} represented by this {@link Call.Details}.
+ */
+ public final @CallState int getState() {
+ return mState;
+ }
+
/** {@hide} */
@TestApi
public String getTelecomCallId() {
@@ -1069,6 +1098,7 @@
if (o instanceof Details) {
Details d = (Details) o;
return
+ Objects.equals(mState, d.mState) &&
Objects.equals(mHandle, d.mHandle) &&
Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
@@ -1095,7 +1125,8 @@
@Override
public int hashCode() {
- return Objects.hash(mHandle,
+ return Objects.hash(mState,
+ mHandle,
mHandlePresentation,
mCallerDisplayName,
mCallerDisplayNamePresentation,
@@ -1117,6 +1148,7 @@
/** {@hide} */
public Details(
+ @CallState int state,
String telecomCallId,
Uri handle,
int handlePresentation,
@@ -1136,6 +1168,7 @@
String contactDisplayName,
int callDirection,
int callerNumberVerificationStatus) {
+ mState = state;
mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -1160,6 +1193,7 @@
/** {@hide} */
public static Details createFromParcelableCall(ParcelableCall parcelableCall) {
return new Details(
+ parcelableCall.getState(),
parcelableCall.getId(),
parcelableCall.getHandle(),
parcelableCall.getHandlePresentation(),
@@ -1186,6 +1220,8 @@
StringBuilder sb = new StringBuilder();
sb.append("[id: ");
sb.append(mTelecomCallId);
+ sb.append(", state: ");
+ sb.append(Call.stateToString(mState));
sb.append(", pa: ");
sb.append(mAccountHandle);
sb.append(", hdl: ");
@@ -1302,7 +1338,7 @@
* @param call The {@code Call} invoking this method.
* @param state The new state of the {@code Call}.
*/
- public void onStateChanged(Call call, int state) {}
+ public void onStateChanged(Call call, @CallState int state) {}
/**
* Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}.
@@ -2171,9 +2207,11 @@
/**
* Obtains the state of this {@code Call}.
*
- * @return A state value, chosen from the {@code STATE_*} constants.
+ * @return The call state.
+ * @deprecated The call state is available via {@link Call.Details#getState()}.
*/
- public int getState() {
+ @Deprecated
+ public @CallState int getState() {
return mState;
}
@@ -2551,6 +2589,30 @@
final void internalSetDisconnected() {
if (mState != Call.STATE_DISCONNECTED) {
mState = Call.STATE_DISCONNECTED;
+ if (mDetails != null) {
+ mDetails = new Details(mState,
+ mDetails.getTelecomCallId(),
+ mDetails.getHandle(),
+ mDetails.getHandlePresentation(),
+ mDetails.getCallerDisplayName(),
+ mDetails.getCallerDisplayNamePresentation(),
+ mDetails.getAccountHandle(),
+ mDetails.getCallCapabilities(),
+ mDetails.getCallProperties(),
+ mDetails.getDisconnectCause(),
+ mDetails.getConnectTimeMillis(),
+ mDetails.getGatewayInfo(),
+ mDetails.getVideoState(),
+ mDetails.getStatusHints(),
+ mDetails.getExtras(),
+ mDetails.getIntentExtras(),
+ mDetails.getCreationTimeMillis(),
+ mDetails.getContactDisplayName(),
+ mDetails.getCallDirection(),
+ mDetails.getCallerNumberVerificationStatus()
+ );
+ fireDetailsChanged(mDetails);
+ }
fireStateChanged(mState);
fireCallDestroyed();
}
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 182dc8b..320308c 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -399,7 +399,7 @@
}
/** The current state of the call. */
- public int getState() {
+ public @Call.CallState int getState() {
return mState;
}
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 4ae11b8..23cf511 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -447,6 +447,9 @@
DataFailCause.VSNCP_RECONNECT_NOT_ALLOWED,
DataFailCause.IPV6_PREFIX_UNAVAILABLE,
DataFailCause.HANDOFF_PREFERENCE_CHANGED,
+ DataFailCause.SLICE_REJECTED,
+ DataFailCause.MATCH_ALL_RULE_NOT_ALLOWED,
+ DataFailCause.ALL_MATCHING_RULES_FAILED,
DataFailCause.OEM_DCFAILCAUSE_1,
DataFailCause.OEM_DCFAILCAUSE_2,
DataFailCause.OEM_DCFAILCAUSE_3,
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 597fe8f..957f683 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -68,22 +68,22 @@
public final boolean isEnDcAvailable;
/**
- * Provides network support info for LTE VoPS and LTE Emergency bearer support
+ * Provides network support info for VoPS and Emergency bearer support
*/
@Nullable
- private final LteVopsSupportInfo mLteVopsSupportInfo;
+ private final VopsSupportInfo mVopsSupportInfo;
/**
* @hide
*/
DataSpecificRegistrationInfo(
int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable,
- boolean isEnDcAvailable, @Nullable LteVopsSupportInfo lteVops) {
+ boolean isEnDcAvailable, @Nullable VopsSupportInfo vops) {
this.maxDataCalls = maxDataCalls;
this.isDcNrRestricted = isDcNrRestricted;
this.isNrAvailable = isNrAvailable;
this.isEnDcAvailable = isEnDcAvailable;
- this.mLteVopsSupportInfo = lteVops;
+ this.mVopsSupportInfo = vops;
}
/**
@@ -97,7 +97,7 @@
isDcNrRestricted = dsri.isDcNrRestricted;
isNrAvailable = dsri.isNrAvailable;
isEnDcAvailable = dsri.isEnDcAvailable;
- mLteVopsSupportInfo = dsri.mLteVopsSupportInfo;
+ mVopsSupportInfo = dsri.mVopsSupportInfo;
}
private DataSpecificRegistrationInfo(/* @NonNull */ Parcel source) {
@@ -105,7 +105,7 @@
isDcNrRestricted = source.readBoolean();
isNrAvailable = source.readBoolean();
isEnDcAvailable = source.readBoolean();
- mLteVopsSupportInfo = LteVopsSupportInfo.CREATOR.createFromParcel(source);
+ mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader());
}
@Override
@@ -114,7 +114,7 @@
dest.writeBoolean(isDcNrRestricted);
dest.writeBoolean(isNrAvailable);
dest.writeBoolean(isEnDcAvailable);
- mLteVopsSupportInfo.writeToParcel(dest, flags);
+ dest.writeParcelable(mVopsSupportInfo, flags);
}
@Override
@@ -131,15 +131,15 @@
.append(" isDcNrRestricted = " + isDcNrRestricted)
.append(" isNrAvailable = " + isNrAvailable)
.append(" isEnDcAvailable = " + isEnDcAvailable)
- .append(" " + mLteVopsSupportInfo)
+ .append(" " + mVopsSupportInfo)
.append(" }")
.toString();
}
@Override
public int hashCode() {
- return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable,
- mLteVopsSupportInfo);
+ return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable,
+ isEnDcAvailable, mVopsSupportInfo);
}
@Override
@@ -153,7 +153,7 @@
&& this.isDcNrRestricted == other.isDcNrRestricted
&& this.isNrAvailable == other.isNrAvailable
&& this.isEnDcAvailable == other.isEnDcAvailable
- && Objects.equals(mLteVopsSupportInfo, other.mLteVopsSupportInfo);
+ && Objects.equals(mVopsSupportInfo, other.mVopsSupportInfo);
}
public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR =
@@ -171,10 +171,26 @@
/**
* @return The LTE VOPS (Voice over Packet Switched) support information
+ *
+ * @deprecated use {@link #getVopsSupportInfo()}
*/
+ @Deprecated
@NonNull
public LteVopsSupportInfo getLteVopsSupportInfo() {
- return mLteVopsSupportInfo;
+ return mVopsSupportInfo instanceof LteVopsSupportInfo
+ ? (LteVopsSupportInfo) mVopsSupportInfo
+ : new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+ LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
}
+ /**
+ * @return The VOPS (Voice over Packet Switched) support information.
+ *
+ * The instance of {@link LTEVopsSupportInfo}, or {@link NrVopsSupportInfo},
+ * null if there is there is no VOPS support information available.
+ */
+ @Nullable
+ public VopsSupportInfo getVopsSupportInfo() {
+ return mVopsSupportInfo;
+ }
}
diff --git a/telephony/java/android/telephony/DataThrottlingRequest.java b/telephony/java/android/telephony/DataThrottlingRequest.java
index f50bb58..2827e8d 100644
--- a/telephony/java/android/telephony/DataThrottlingRequest.java
+++ b/telephony/java/android/telephony/DataThrottlingRequest.java
@@ -17,6 +17,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -52,6 +53,9 @@
* @hide
*/
@SystemApi
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
public static final int DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER = 1;
/**
@@ -63,6 +67,9 @@
* @hide
*/
@SystemApi
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
public static final int DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER = 2;
/**
@@ -76,6 +83,9 @@
* @hide
*/
@SystemApi
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
public static final int DATA_THROTTLING_ACTION_HOLD = 3;
/**
diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.java b/telephony/java/android/telephony/LteVopsSupportInfo.java
index 83e41bf..87761e2 100644
--- a/telephony/java/android/telephony/LteVopsSupportInfo.java
+++ b/telephony/java/android/telephony/LteVopsSupportInfo.java
@@ -21,7 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
-import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -32,7 +32,7 @@
* @hide
*/
@SystemApi
-public final class LteVopsSupportInfo implements Parcelable {
+public final class LteVopsSupportInfo extends VopsSupportInfo {
/**@hide*/
@Retention(RetentionPolicy.SOURCE)
@@ -42,7 +42,10 @@
public @interface LteVopsStatus {}
/**
* Indicates information not available from modem.
+ *
+ * @deprecated as no instance will be created in this case
*/
+ @Deprecated
public static final int LTE_STATUS_NOT_AVAILABLE = 1;
/**
@@ -82,13 +85,38 @@
return mEmcBearerSupport;
}
+ /**
+ * Returns whether VoPS is supported by the network
+ */
+ @Override
+ public boolean isVopsSupported() {
+ return mVopsSupport == LTE_STATUS_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service is supported by the network
+ */
+ @Override
+ public boolean isEmergencyServiceSupported() {
+ return mEmcBearerSupport == LTE_STATUS_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service fallback is supported by the network
+ */
+ @Override
+ public boolean isEmergencyServiceFallbackSupported() {
+ return false;
+ }
+
@Override
public int describeContents() {
return 0;
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags, AccessNetworkType.EUTRAN);
out.writeInt(mVopsSupport);
out.writeInt(mEmcBearerSupport);
}
@@ -124,6 +152,8 @@
new Creator<LteVopsSupportInfo>() {
@Override
public LteVopsSupportInfo createFromParcel(Parcel in) {
+ // Skip the type info.
+ in.readInt();
return new LteVopsSupportInfo(in);
}
@@ -133,6 +163,11 @@
}
};
+ /** @hide */
+ protected static LteVopsSupportInfo createFromParcelBody(Parcel in) {
+ return new LteVopsSupportInfo(in);
+ }
+
private LteVopsSupportInfo(Parcel in) {
mVopsSupport = in.readInt();
mEmcBearerSupport = in.readInt();
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index a78f813..5fb60d7 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -293,11 +293,12 @@
@Nullable CellIdentity cellIdentity, @Nullable String rplmn,
int maxDataCalls, boolean isDcNrRestricted,
boolean isNrAvailable, boolean isEndcAvailable,
- LteVopsSupportInfo lteVopsSupportInfo) {
+ @Nullable VopsSupportInfo vopsSupportInfo) {
this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
emergencyOnly, availableServices, cellIdentity, rplmn);
mDataSpecificInfo = new DataSpecificRegistrationInfo(
- maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo);
+ maxDataCalls, isDcNrRestricted, isNrAvailable,
+ isEndcAvailable, vopsSupportInfo);
updateNrState();
}
diff --git a/telephony/java/android/telephony/NrVopsSupportInfo.aidl b/telephony/java/android/telephony/NrVopsSupportInfo.aidl
new file mode 100644
index 0000000..460a589
--- /dev/null
+++ b/telephony/java/android/telephony/NrVopsSupportInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony;
+
+parcelable NrVopsSupportInfo;
diff --git a/telephony/java/android/telephony/NrVopsSupportInfo.java b/telephony/java/android/telephony/NrVopsSupportInfo.java
new file mode 100644
index 0000000..155ee38
--- /dev/null
+++ b/telephony/java/android/telephony/NrVopsSupportInfo.java
@@ -0,0 +1,260 @@
+/*
+ * 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Class stores information related to NR network VoPS support
+ * @hide
+ */
+@SystemApi
+public final class NrVopsSupportInfo extends VopsSupportInfo {
+
+ /**
+ * Indicates network does not support vops
+ */
+ public static final int NR_STATUS_VOPS_NOT_SUPPORTED = 0;
+
+ /**
+ * Indicates network supports vops over 3gpp access.
+ */
+ public static final int NR_STATUS_VOPS_3GPP_SUPPORTED = 1;
+
+ /**
+ * Indicates network supports vops over non 3gpp access
+ */
+ public static final int NR_STATUS_VOPS_NON_3GPP_SUPPORTED = 2;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"NR_STATUS_VOPS_"},
+ value = {
+ NR_STATUS_VOPS_NOT_SUPPORTED,
+ NR_STATUS_VOPS_3GPP_SUPPORTED,
+ NR_STATUS_VOPS_NON_3GPP_SUPPORTED
+ })
+ public @interface NrVopsStatus {}
+
+ /**
+ * Indicates network does not support emergency service
+ */
+ public static final int NR_STATUS_EMC_NOT_SUPPORTED = 0;
+
+ /**
+ * Indicates network supports emergency service in NR connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMC_5GCN_ONLY = 1;
+
+ /**
+ * Indicates network supports emergency service in E-UTRA connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMC_EUTRA_5GCN_ONLY = 2;
+
+ /**
+ * Indicates network supports emergency service in NR connected to 5GCN and
+ * E-UTRA connected to 5GCN
+ */
+ public static final int NR_STATUS_EMC_NR_EUTRA_5GCN = 3;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"NR_STATUS_EMC_"},
+ value = {
+ NR_STATUS_EMC_NOT_SUPPORTED,
+ NR_STATUS_EMC_5GCN_ONLY,
+ NR_STATUS_EMC_EUTRA_5GCN_ONLY,
+ NR_STATUS_EMC_NR_EUTRA_5GCN
+ })
+ public @interface NrEmcStatus {}
+
+ /**
+ * Indicates network does not support emergency service
+ */
+ public static final int NR_STATUS_EMF_NOT_SUPPORTED = 0;
+
+ /**
+ * Indicates network supports emergency service fallback in NR connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMF_5GCN_ONLY = 1;
+
+ /**
+ * Indicates network supports emergency service fallback in E-UTRA connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMF_EUTRA_5GCN_ONLY = 2;
+
+ /**
+ * Indicates network supports emergency service fallback in NR connected to 5GCN
+ * and E-UTRA connected to 5GCN
+ */
+ public static final int NR_STATUS_EMF_NR_EUTRA_5GCN = 3;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"NR_STATUS_EMF_"},
+ value = {
+ NR_STATUS_EMF_NOT_SUPPORTED,
+ NR_STATUS_EMF_5GCN_ONLY,
+ NR_STATUS_EMF_EUTRA_5GCN_ONLY,
+ NR_STATUS_EMF_NR_EUTRA_5GCN
+ })
+ public @interface NrEmfStatus {}
+
+ @NrVopsStatus
+ private final int mVopsSupport;
+ @NrEmcStatus
+ private final int mEmcSupport;
+ @NrEmfStatus
+ private final int mEmfSupport;
+
+ public NrVopsSupportInfo(@NrVopsStatus int vops, @NrEmcStatus int emc, @NrEmcStatus int emf) {
+ mVopsSupport = vops;
+ mEmcSupport = emc;
+ mEmfSupport = emf;
+ }
+
+ /**
+ * Provides the NR VoPS support capability as described in:
+ * 3GPP 24.501 EPS network feature support -> IMS VoPS
+ */
+ public @NrVopsStatus int getVopsSupport() {
+ return mVopsSupport;
+ }
+
+ /**
+ * Provides the NR Emergency bearer support capability as described in:
+ * 3GPP 24.501 EPS network feature support -> EMC, and
+ * 38.331 SIB1 : ims-EmergencySupport
+ */
+ public @NrEmcStatus int getEmcSupport() {
+ return mEmcSupport;
+ }
+
+ /**
+ * Provides the NR emergency service fallback support capability as
+ * described in 3GPP 24.501 EPS network feature support -> EMF
+ */
+ public @NrEmfStatus int getEmfSupport() {
+ return mEmfSupport;
+ }
+
+ /**
+ * Returns whether VoPS is supported by the network
+ */
+ @Override
+ public boolean isVopsSupported() {
+ return mVopsSupport != NR_STATUS_VOPS_NOT_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service is supported by the network
+ */
+ @Override
+ public boolean isEmergencyServiceSupported() {
+ return mEmcSupport != NR_STATUS_EMC_NOT_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service fallback is supported by the network
+ */
+ public boolean isEmergencyServiceFallbackSupported() {
+ return mEmfSupport != NR_STATUS_EMF_NOT_SUPPORTED;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags, AccessNetworkType.NGRAN);
+ out.writeInt(mVopsSupport);
+ out.writeInt(mEmcSupport);
+ out.writeInt(mEmfSupport);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null || !(o instanceof NrVopsSupportInfo)) {
+ return false;
+ }
+ if (this == o) return true;
+ NrVopsSupportInfo other = (NrVopsSupportInfo) o;
+ return mVopsSupport == other.mVopsSupport
+ && mEmcSupport == other.mEmcSupport
+ && mEmfSupport == other.mEmfSupport;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mVopsSupport, mEmcSupport, mEmfSupport);
+ }
+
+ /**
+ * @return string representation.
+ */
+ @NonNull
+ @Override
+ public String toString() {
+ return ("NrVopsSupportInfo : "
+ + " mVopsSupport = " + mVopsSupport
+ + " mEmcSupport = " + mEmcSupport
+ + " mEmfSupport = " + mEmfSupport);
+ }
+
+ public static final @android.annotation.NonNull Creator<NrVopsSupportInfo> CREATOR =
+ new Creator<NrVopsSupportInfo>() {
+ @Override
+ public NrVopsSupportInfo createFromParcel(Parcel in) {
+ // Skip the type info.
+ in.readInt();
+ return new NrVopsSupportInfo(in);
+ }
+
+ @Override
+ public NrVopsSupportInfo[] newArray(int size) {
+ return new NrVopsSupportInfo[size];
+ }
+ };
+
+ /** @hide */
+ protected static NrVopsSupportInfo createFromParcelBody(Parcel in) {
+ return new NrVopsSupportInfo(in);
+ }
+
+ private NrVopsSupportInfo(Parcel in) {
+ mVopsSupport = in.readInt();
+ mEmcSupport = in.readInt();
+ mEmfSupport = in.readInt();
+ }
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 17af463..a527e8d 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2219,7 +2219,7 @@
ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
}
} catch (RemoteException ex) {
- throw new RuntimeException(ex);
+ Log.e(TAG, "getSmsCapacityOnIcc() RemoteException", ex);
}
return ret;
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f7580d7..a46621a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -630,7 +630,7 @@
D2D_SHARING_STARRED_CONTACTS,
D2D_SHARING_ALL
})
- public @interface DeviceToDeviceStatusSharing {}
+ public @interface DeviceToDeviceStatusSharingPreference {}
/**
* TelephonyProvider column name for device to device sharing status.
@@ -3415,29 +3415,31 @@
* app uses this method to indicate with whom they wish to share device to device status
* information.
* @param sharing the status sharing preference
- * @param subId the unique Subscription ID in database
+ * @param subscriptionId the unique Subscription ID in database
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
- public void setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharing int sharing,
- int subId) {
+ public void setDeviceToDeviceStatusSharingPreference(
+ @DeviceToDeviceStatusSharingPreference int sharing, int subscriptionId) {
if (VDBG) {
- logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: " + subId);
+ logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: "
+ + subscriptionId);
}
- setSubscriptionPropertyHelper(subId, "setDeviceToDeviceSharingStatus",
- (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subId));
+ setSubscriptionPropertyHelper(subscriptionId, "setDeviceToDeviceSharingStatus",
+ (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subscriptionId));
}
/**
* Returns the user-chosen device to device status sharing preference
- * @param subId Subscription id of subscription
+ * @param subscriptionId Subscription id of subscription
* @return The device to device status sharing preference
*/
- public @DeviceToDeviceStatusSharing int getDeviceToDeviceStatusSharing(int subId) {
+ public @DeviceToDeviceStatusSharingPreference int getDeviceToDeviceStatusSharingPreference(
+ int subscriptionId) {
if (VDBG) {
- logd("[getDeviceToDeviceStatusSharing] + subId: " + subId);
+ logd("[getDeviceToDeviceStatusSharing] + subId: " + subscriptionId);
}
- return getIntegerSubscriptionProperty(subId, D2D_STATUS_SHARING, D2D_SHARING_DISABLED,
- mContext);
+ return getIntegerSubscriptionProperty(subscriptionId, D2D_STATUS_SHARING,
+ D2D_SHARING_DISABLED, mContext);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f06b538..d3246ca 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11037,6 +11037,25 @@
}
/**
+ * Determines the {@link PhoneAccountHandle} associated with this TelephonyManager.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+ *
+ * <p>Requires Permission android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE or that the
+ * calling app has carrier privileges (see {@link #hasCarrierPrivileges})
+ *
+ * @return The {@link PhoneAccountHandle} associated with the TelphonyManager, or {@code null}
+ * if there is no associated {@link PhoneAccountHandle}; this can happen if the subscription is
+ * data-only or an opportunistic subscription.
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @Nullable PhoneAccountHandle getPhoneAccountHandle() {
+ return getPhoneAccountHandleForSubscriptionId(getSubId());
+ }
+
+ /**
* Determines the {@link PhoneAccountHandle} associated with a subscription Id.
*
* @param subscriptionId The subscription Id to check.
@@ -12388,12 +12407,6 @@
* "Data capable" means that this device supports packet-switched
* data connections over the telephony network.
* <p>
- * Note: the meaning of this flag is subtly different from the
- * PackageManager.FEATURE_TELEPHONY system feature, which is available
- * on any device with a telephony radio, even if the device is
- * voice-only.
- *
- * @hide
*/
public boolean isDataCapable() {
if (mContext == null) return true;
@@ -14928,12 +14941,23 @@
public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE =
"CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
+ /**
+ * Indicates whether a data throttling request sent with {@link #sendThermalMitigationRequest}
+ * is supported. See comments on {@link #sendThermalMitigationRequest} for more information.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING =
+ "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "CAPABILITY_", value = {
CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
- CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE
+ CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE,
+ CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING,
})
public @interface RadioInterfaceCapability {}
@@ -15043,11 +15067,24 @@
* and can be used at any time during data throttling to hold onto the current level of data
* throttling.
*
+ * <p> If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}({@link
+ * #CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING}) returns false, then sending a {@link
+ * DataThrottlingRequest#DATA_THROTTLING_ACTION_HOLD}, {@link
+ * DataThrottlingRequest#DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER}, or {@link
+ * DataThrottlingRequest#DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER} will result in {@link
+ * IllegalArgumentException} being thrown. However, on devices that do not
+ * support data throttling, {@link
+ * DataThrottlingRequest#DATA_THROTTLING_ACTION_NO_DATA_THROTTLING} can still be requested in
+ * order to undo the mitigations above it (i.e {@link
+ * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_VOICE_ONLY} and/or {@link
+ * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}).
+ *
* @param thermalMitigationRequest Thermal mitigation request. See {@link
* ThermalMitigationRequest} for details.
*
* @throws IllegalStateException if the Telephony process is not currently available.
- * @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters.
+ * @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters or
+ * if the device's modem does not support data throttling.
*
* @hide
*/
diff --git a/telephony/java/android/telephony/VopsSupportInfo.aidl b/telephony/java/android/telephony/VopsSupportInfo.aidl
new file mode 100644
index 0000000..31c608f
--- /dev/null
+++ b/telephony/java/android/telephony/VopsSupportInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony;
+
+parcelable VopsSupportInfo;
diff --git a/telephony/java/android/telephony/VopsSupportInfo.java b/telephony/java/android/telephony/VopsSupportInfo.java
new file mode 100644
index 0000000..f89bfa9
--- /dev/null
+++ b/telephony/java/android/telephony/VopsSupportInfo.java
@@ -0,0 +1,103 @@
+/*
+ * 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.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+
+/**
+ * Abstract base class for the information related to network VoPS support.
+ * This is the base class for XxVopsSupportInfo which represent VoPS support
+ * information for specific network access techonology.
+ * @hide
+ */
+@SuppressLint("ParcelNotFinal")
+@SystemApi
+public abstract class VopsSupportInfo implements Parcelable {
+
+ /**
+ * @hide
+ */
+ public VopsSupportInfo() {}
+
+ /**
+ * Returns whether VoPS is supported by the network
+ */
+ public abstract boolean isVopsSupported();
+
+ /**
+ * Returns whether emergency service is supported by the network
+ */
+ public abstract boolean isEmergencyServiceSupported();
+
+ /**
+ * Returns whether emergency service fallback is supported by the network
+ */
+ public abstract boolean isEmergencyServiceFallbackSupported();
+
+ /**
+ * Implement the Parcelable interface
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public abstract void writeToParcel(@NonNull Parcel dest, int flags);
+
+ /**
+ * Used by child classes for parceling.
+ *
+ * @hide
+ */
+ protected void writeToParcel(@NonNull Parcel dest, int flags, int type) {
+ dest.writeInt(type);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<VopsSupportInfo> CREATOR =
+ new Creator<VopsSupportInfo>() {
+ @Override
+ public VopsSupportInfo createFromParcel(Parcel in) {
+ int type = in.readInt();
+ switch (type) {
+ case AccessNetworkType.EUTRAN:
+ return LteVopsSupportInfo.createFromParcelBody(in);
+ case AccessNetworkType.NGRAN:
+ return NrVopsSupportInfo.createFromParcelBody(in);
+ default: throw new RuntimeException("Bad VopsSupportInfo Parcel");
+ }
+ }
+
+ @Override
+ public VopsSupportInfo[] newArray(int size) {
+ return new VopsSupportInfo[size];
+ }
+ };
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public abstract boolean equals(Object o);
+}
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 52d0f03..a133ead 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -29,7 +29,9 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Contains the User Capability Exchange capabilities corresponding to a contact's URI.
@@ -110,7 +112,6 @@
/**
* Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
* queried through SIP OPTIONS.
- * @hide
*/
public static final class OptionsBuilder {
@@ -151,7 +152,7 @@
* @param tags the list of the supported feature tags
* @return this OptionBuilder
*/
- public @NonNull OptionsBuilder addFeatureTags(@NonNull List<String> tags) {
+ public @NonNull OptionsBuilder addFeatureTags(@NonNull Set<String> tags) {
mCapabilities.mFeatureTags.addAll(tags);
return this;
}
@@ -220,7 +221,7 @@
private @CapabilityMechanism int mCapabilityMechanism;
private @RequestResult int mRequestResult;
- private final List<String> mFeatureTags = new ArrayList<>();
+ private final Set<String> mFeatureTags = new HashSet<>();
private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();
private RcsContactUceCapability(@NonNull Uri contactUri, @CapabilityMechanism int mechanism,
@@ -235,7 +236,9 @@
mCapabilityMechanism = in.readInt();
mSourceType = in.readInt();
mRequestResult = in.readInt();
- in.readStringList(mFeatureTags);
+ List<String> featureTagList = new ArrayList<>();
+ in.readStringList(featureTagList);
+ mFeatureTags.addAll(featureTagList);
in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
}
@@ -245,7 +248,7 @@
out.writeInt(mCapabilityMechanism);
out.writeInt(mSourceType);
out.writeInt(mRequestResult);
- out.writeStringList(mFeatureTags);
+ out.writeStringList(new ArrayList<>(mFeatureTags));
out.writeParcelableList(mPresenceTuples, flags);
}
@@ -285,7 +288,20 @@
if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
return Collections.emptyList();
}
- return Collections.unmodifiableList(mFeatureTags);
+ return Collections.unmodifiableList(new ArrayList<>(mFeatureTags));
+ }
+
+ /**
+ * @return The feature tags present in the OPTIONS response from the network.
+ * <p>
+ * Note: this is only populated if {@link #getCapabilityMechanism} is
+ * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS}
+ */
+ public @NonNull Set<String> getFeatureTags() {
+ if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
+ return Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(mFeatureTags);
}
/**
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index a217d13..c3d7325 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -26,7 +26,8 @@
import android.telephony.ims.stub.CapabilityExchangeEventListener;
import android.util.Log;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
/**
* The ICapabilityExchangeEventListener wrapper class to store the listener which is registered by
@@ -84,7 +85,7 @@
* request to the framework.
*/
public void onRemoteCapabilityRequest(@NonNull Uri contactUri,
- @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
+ @NonNull Set<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
throws ImsException {
ICapabilityExchangeEventListener listener = mListenerBinder;
if (listener == null) {
@@ -114,7 +115,8 @@
};
try {
- listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback);
+ listener.onRemoteCapabilityRequest(contactUri, new ArrayList<>(remoteCapabilities),
+ internalCallback);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote capability request exception: " + e);
throw new ImsException("Remote is not available",
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 85703f8..6315b24 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -47,6 +47,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
@@ -145,8 +146,8 @@
throws RemoteException {
OptionsResponseCallback callbackWrapper = new RcsOptionsResponseAidlWrapper(callback);
executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal()
- .sendOptionsCapabilityRequest(contactUri, myCapabilities, callbackWrapper),
- "sendOptionsCapabilityRequest");
+ .sendOptionsCapabilityRequest(contactUri, new HashSet<>(myCapabilities),
+ callbackWrapper), "sendOptionsCapabilityRequest");
}
// Call the methods with a clean calling identity on the executor and wait indefinitely for
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index 6295548..a3be8da 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -26,7 +26,7 @@
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
-import java.util.List;
+import java.util.Set;
/**
* The interface that is used by the framework to listen to events from the vendor RCS stack
@@ -98,7 +98,8 @@
* {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError}.
* @param contactUri The URI associated with the remote contact that is
* requesting capabilities.
- * @param remoteCapabilities The remote contact's capability information.
+ * @param remoteCapabilities The remote contact's capability information. The capability
+ * information is in the format defined in RCC.07 section 2.6.1.3.
* @param callback The callback of this request which is sent from the remote user.
* @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
* currently connected to the framework. This can happen if the {@link RcsFeature} is not
@@ -107,6 +108,6 @@
* cases when the Telephony stack has crashed.
*/
void onRemoteCapabilityRequest(@NonNull Uri contactUri,
- @NonNull List<String> remoteCapabilities,
+ @NonNull Set<String> remoteCapabilities,
@NonNull OptionsRequestCallback callback) throws ImsException;
}
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index 03e17fb..25b9446 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -33,6 +33,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -433,6 +434,7 @@
* @param contactUri The URI of the remote user that we wish to get the capabilities of.
* @param myCapabilities The capabilities of this device to send to the remote user.
* @param callback The callback of this request which is sent from the remote user.
+ * @hide
*/
// executor used is defined in the constructor.
@SuppressLint("ExecutorRegistration")
@@ -446,4 +448,27 @@
// Do not do anything, this is a stub implementation.
}
}
+
+ /**
+ * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism
+ * in order to receive the capabilities of the remote user in response.
+ * <p>
+ * The implementer must use {@link OptionsResponseCallback} to send the response of
+ * this query from the network back to the framework.
+ * @param contactUri The URI of the remote user that we wish to get the capabilities of.
+ * @param myCapabilities The capabilities of this device to send to the remote user.
+ * @param callback The callback of this request which is sent from the remote user.
+ */
+ // executor used is defined in the constructor.
+ @SuppressLint("ExecutorRegistration")
+ public void sendOptionsCapabilityRequest(@NonNull Uri contactUri,
+ @NonNull Set<String> myCapabilities, @NonNull OptionsResponseCallback callback) {
+ // Stub - to be implemented by service
+ Log.w(LOG_TAG, "sendOptionsCapabilityRequest called with no implementation.");
+ try {
+ callback.onCommandError(COMMAND_CODE_NOT_SUPPORTED);
+ } catch (ImsException e) {
+ // Do not do anything, this is a stub implementation.
+ }
+ }
}
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
index b9b347b..c1a86b3 100644
--- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -87,7 +87,7 @@
assertEquals(down.id, eventId)
// Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
- assigner.onChoreographerCallback()
+ assigner.notifyFrameProcessed()
eventId = assigner.processEvent(move3)
assertEquals(move3.id, eventId)
eventId = assigner.processEvent(move4)
@@ -122,7 +122,7 @@
eventId = assigner.processEvent(up)
// DOWN is only sticky for Motions, not for keys
assertEquals(up.id, eventId)
- assigner.onChoreographerCallback()
+ assigner.notifyFrameProcessed()
val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22)
eventId = assigner.processEvent(down2)
assertEquals(down2.id, eventId)
diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
index 4f95ce5..b134fe7 100644
--- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
@@ -17,6 +17,7 @@
package com.android.test.input
import android.os.HandlerThread
+import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.Looper
import android.view.InputChannel
import android.view.InputEvent
@@ -24,7 +25,8 @@
import android.view.InputEventSender
import android.view.KeyEvent
import android.view.MotionEvent
-import java.util.concurrent.CountDownLatch
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
import org.junit.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -44,41 +46,44 @@
assertEquals(expected.displayId, received.displayId)
}
+private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T {
+ try {
+ return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS)
+ } catch (e: InterruptedException) {
+ throw RuntimeException("Unexpectedly interrupted while waiting for event")
+ }
+}
+
class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
InputEventReceiver(channel, looper) {
- companion object {
- const val TAG = "TestInputEventReceiver"
- }
-
- var lastEvent: InputEvent? = null
+ private val mInputEvents = LinkedBlockingQueue<InputEvent>()
override fun onInputEvent(event: InputEvent) {
- lastEvent = when (event) {
- is KeyEvent -> KeyEvent.obtain(event)
- is MotionEvent -> MotionEvent.obtain(event)
+ when (event) {
+ is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event))
+ is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event))
else -> throw Exception("Received $event is neither a key nor a motion")
}
finishInputEvent(event, true /*handled*/)
}
+
+ fun getInputEvent(): InputEvent {
+ return getEvent(mInputEvents)
+ }
}
class TestInputEventSender(channel: InputChannel, looper: Looper) :
InputEventSender(channel, looper) {
- companion object {
- const val TAG = "TestInputEventSender"
- }
- data class FinishedResult(val seq: Int, val handled: Boolean)
+ data class FinishedSignal(val seq: Int, val handled: Boolean)
- private var mFinishedSignal = CountDownLatch(1)
+ private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
+
override fun onInputEventFinished(seq: Int, handled: Boolean) {
- finishedResult = FinishedResult(seq, handled)
- mFinishedSignal.countDown()
+ mFinishedSignals.put(FinishedSignal(seq, handled))
}
- lateinit var finishedResult: FinishedResult
- fun waitForFinish() {
- mFinishedSignal.await()
- mFinishedSignal = CountDownLatch(1) // Ready for next event
+ fun getFinishedSignal(): FinishedSignal {
+ return getEvent(mFinishedSignals)
}
}
@@ -111,13 +116,13 @@
KeyEvent.KEYCODE_A, 0 /*repeat*/)
val seq = 10
mSender.sendInputEvent(seq, key)
- mSender.waitForFinish()
+ val receivedKey = mReceiver.getInputEvent() as KeyEvent
+ val finishedSignal = mSender.getFinishedSignal()
// Check receiver
- assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent)
+ assertKeyEvent(key, receivedKey)
// Check sender
- assertEquals(seq, mSender.finishedResult.seq)
- assertEquals(true, mSender.finishedResult.handled)
+ assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal)
}
}
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index a4d8353..fd126ad 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -44,7 +44,7 @@
setPartialConnectivityAcceptable(false)
setUnvalidatedConnectivityAcceptable(true)
}.build()
- assertParcelSane(config, 10)
+ assertParcelSane(config, 12)
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 26ae099..9946515 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -515,11 +515,22 @@
assertFalse(nc1.equalsNetCapabilities(nc2));
nc2.addUnwantedCapability(NET_CAPABILITY_INTERNET);
assertTrue(nc1.equalsNetCapabilities(nc2));
+ if (isAtLeastS()) {
+ // Remove a required capability doesn't affect unwanted capabilities.
+ // This is a behaviour change from S.
+ nc1.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
- nc1.removeCapability(NET_CAPABILITY_INTERNET);
- assertFalse(nc1.equalsNetCapabilities(nc2));
- nc2.removeCapability(NET_CAPABILITY_INTERNET);
- assertTrue(nc1.equalsNetCapabilities(nc2));
+ nc1.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+ assertFalse(nc1.equalsNetCapabilities(nc2));
+ nc2.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
+ } else {
+ nc1.removeCapability(NET_CAPABILITY_INTERNET);
+ assertFalse(nc1.equalsNetCapabilities(nc2));
+ nc2.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
+ }
}
@Test
@@ -580,11 +591,21 @@
// This will effectively move NOT_ROAMING capability from required to unwanted for nc1.
nc1.addUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
- nc2.combineCapabilities(nc1);
- // We will get this capability in both requested and unwanted lists thus this request
- // will never be satisfied.
- assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
- assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+ if (isAtLeastS()) {
+ // From S, it is not allowed to have the same capability in both wanted and
+ // unwanted list.
+ assertThrows(IllegalArgumentException.class, () -> nc2.combineCapabilities(nc1));
+ // Remove unwanted capability to continue other tests.
+ nc1.removeUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
+ } else {
+ nc2.combineCapabilities(nc1);
+ // We will get this capability in both requested and unwanted lists thus this request
+ // will never be satisfied.
+ assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+ assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+ // For R or below, remove unwanted capability via removeCapability.
+ nc1.removeCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
nc1.setSSID(TEST_SSID);
nc2.combineCapabilities(nc1);
diff --git a/tests/net/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
index 01eb514..61ef5bd 100644
--- a/tests/net/integration/src/android/net/TestNetworkStackClient.kt
+++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
@@ -19,6 +19,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.net.networkstack.NetworkStackClientBase
import android.os.IBinder
import com.android.server.net.integrationtests.TestNetworkStackService
import org.mockito.Mockito.any
@@ -29,28 +30,22 @@
const val TEST_ACTION_SUFFIX = ".Test"
-class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+class TestNetworkStackClient(private val context: Context) : NetworkStackClientBase() {
// TODO: consider switching to TrackRecord for more expressive checks
private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+ private val moduleConnector = ConnectivityModuleConnector { _, action, _, _ ->
+ val intent = Intent(action)
+ val serviceName = TestNetworkStackService::class.qualifiedName
+ ?: fail("TestNetworkStackService name not found")
+ intent.component = ComponentName(context.packageName, serviceName)
+ return@ConnectivityModuleConnector intent
+ }.also { it.init(context) }
- private class TestDependencies(private val context: Context) : Dependencies {
- override fun addToServiceManager(service: IBinder) = Unit
- override fun checkCallerUid() = Unit
-
- override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
- return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
- getNetworkStackIntent(inSystemProcess)
- }.also { it.init(context) }
- }
-
- private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
- // Simulate out-of-system-process config: in-process service not found (null intent)
- if (inSystemProcess) return null
- val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
- val serviceName = TestNetworkStackService::class.qualifiedName
- ?: fail("TestNetworkStackService name not found")
- intent.component = ComponentName(context.packageName, serviceName)
- return intent
+ fun start() {
+ moduleConnector.startModuleService(
+ INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) { connector ->
+ onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(connector))
}
}
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index db49e0b..14dddcbd 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -157,7 +157,6 @@
doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
networkStackClient = TestNetworkStackClient(realContext)
- networkStackClient.init()
networkStackClient.start()
service = TestConnectivityService(makeDependencies())
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8a50141..db1334a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -216,7 +216,6 @@
import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
-import android.net.NetworkStackClient;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTestResultParcelable;
import android.net.OemNetworkPreferences;
@@ -236,6 +235,7 @@
import android.net.VpnManager;
import android.net.VpnTransportInfo;
import android.net.metrics.IpConnectivityLog;
+import android.net.networkstack.NetworkStackClientBase;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.NetworkMonitorUtils;
@@ -446,7 +446,7 @@
@Mock NetworkStatsManager mStatsManager;
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
- @Mock NetworkStackClient mNetworkStack;
+ @Mock NetworkStackClientBase mNetworkStack;
@Mock PackageManager mPackageManager;
@Mock UserManager mUserManager;
@Mock NotificationManager mNotificationManager;
@@ -1178,11 +1178,6 @@
}
@Override
- public int getNetId() {
- return (mMockNetworkAgent == null) ? NETID_UNSET : mMockNetworkAgent.getNetwork().netId;
- }
-
- @Override
public int getActiveVpnType() {
return mVpnType;
}
@@ -1206,10 +1201,12 @@
mNetworkCapabilities);
mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
- verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()),
+ final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
+ : mMockVpn.getNetwork().getNetId();
+ verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
eq(toUidRangeStableParcels(uids)));
verify(mMockNetd, never())
- .networkRemoveUidRanges(eq(mMockVpn.getNetId()), any());
+ .networkRemoveUidRanges(eq(expectedNetId), any());
mAgentRegistered = true;
updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
@@ -7488,6 +7485,9 @@
final NetworkRequest vpnUidRequest = new NetworkRequest.Builder().build();
registerNetworkCallbackAsUid(vpnUidRequest, vpnUidCallback, VPN_UID);
+ final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback();
+ registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID);
+
final int uid = Process.myUid();
final int userId = UserHandle.getUserId(uid);
final ArrayList<String> allowList = new ArrayList<>();
@@ -7506,6 +7506,7 @@
callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7518,6 +7519,7 @@
callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -7532,6 +7534,7 @@
callback.assertNoCallback();
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
// The following requires that the UID of this test package is greater than VPN_UID. This
// is always true in practice because a plain AOSP build with no apps installed has almost
@@ -7552,6 +7555,7 @@
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
defaultCallback.assertNoCallback();
vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ vpnUidDefaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7572,6 +7576,7 @@
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7583,6 +7588,7 @@
defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7597,6 +7603,7 @@
callback.assertNoCallback();
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7608,6 +7615,7 @@
callback.assertNoCallback();
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7620,6 +7628,7 @@
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7630,6 +7639,7 @@
assertUidRangesUpdatedForMyUid(true);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability.
+ vpnUidDefaultCallback.assertNoCallback(); // VPN does not apply to VPN_UID
assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7640,11 +7650,14 @@
mMockVpn.disconnect();
defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+ vpnUidCallback.assertNoCallback();
+ vpnUidDefaultCallback.assertNoCallback();
assertNull(mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultCallback);
mCm.unregisterNetworkCallback(vpnUidCallback);
+ mCm.unregisterNetworkCallback(vpnUidDefaultCallback);
}
private void setupLegacyLockdownVpn() {
@@ -9745,11 +9758,14 @@
exemptUidCaptor.capture());
assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+ final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
+ : mMockVpn.getNetwork().getNetId();
+
if (add) {
- inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()),
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
eq(toUidRangeStableParcels(vpnRanges)));
} else {
- inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(mMockVpn.getNetId()),
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(expectedNetId),
eq(toUidRangeStableParcels(vpnRanges)));
}