Merge "Trigger transition in ATMS#resizeTask"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 3bbbb15..e0fffb4 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -924,7 +924,8 @@
@SuppressWarnings("UnsafeParcelApi")
private JobInfo(Parcel in) {
jobId = in.readInt();
- extras = in.readPersistableBundle();
+ final PersistableBundle persistableExtras = in.readPersistableBundle();
+ extras = persistableExtras != null ? persistableExtras : PersistableBundle.EMPTY;
transientExtras = in.readBundle();
if (in.readInt() != 0) {
clipData = ClipData.CREATOR.createFromParcel(in);
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 3764249..33668c7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -337,7 +337,7 @@
* but there are situations where it may get this wrong and count the JobInfo as changing.
* (That said, you should be relatively safe with a simple set of consistent data in these
* fields.) You should never use {@link JobInfo.Builder#setClipData(ClipData, int)} with
- * work you are enqueue, since currently this will always be treated as a different JobInfo,
+ * work you are enqueuing, since currently this will always be treated as a different JobInfo,
* even if the ClipData contents are exactly the same.</p>
*
* <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
@@ -345,6 +345,16 @@
* version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
* this API if calls are made too frequently in a short amount of time.
*
+ * <p class="caution"><strong>Note:</strong> Prior to Android version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted.
+ * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw
+ * an {@link IllegalArgumentException} if they attempted to do so. Starting with
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * JobWorkItems can be persisted alongside the hosting job.
+ * However, Intents cannot be persisted. Set a {@link PersistableBundle} using
+ * {@link JobWorkItem.Builder#setExtras(PersistableBundle)} for any information that needs
+ * to be persisted.
+ *
* <p>Note: The JobService component needs to be enabled in order to successfully schedule a
* job.
*
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
index 32945e0..18167e2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
@@ -19,20 +19,33 @@
import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
import android.annotation.BytesLong;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.compat.Compatibility;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
/**
* A unit of work that can be enqueued for a job using
* {@link JobScheduler#enqueue JobScheduler.enqueue}. See
* {@link JobParameters#dequeueWork() JobParameters.dequeueWork} for more details.
+ *
+ * <p class="caution"><strong>Note:</strong> Prior to Android version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted.
+ * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw
+ * an {@link IllegalArgumentException} if they attempted to do so. Starting with
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems can be persisted alongside
+ * the hosting job. However, Intents cannot be persisted. Set a {@link PersistableBundle} using
+ * {@link Builder#setExtras(PersistableBundle)} for any information that needs to be persisted.
*/
final public class JobWorkItem implements Parcelable {
+ @NonNull
+ private final PersistableBundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
final Intent mIntent;
private final long mNetworkDownloadBytes;
@@ -49,6 +62,10 @@
* Create a new piece of work, which can be submitted to
* {@link JobScheduler#enqueue JobScheduler.enqueue}.
*
+ * <p>
+ * Intents cannot be used for persisted JobWorkItems.
+ * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+ *
* @param intent The general Intent describing this work.
*/
public JobWorkItem(Intent intent) {
@@ -62,6 +79,10 @@
* See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
* details about how to estimate network traffic.
*
+ * <p>
+ * Intents cannot be used for persisted JobWorkItems.
+ * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+ *
* @param intent The general Intent describing this work.
* @param downloadBytes The estimated size of network traffic that will be
* downloaded by this job work item, in bytes.
@@ -79,6 +100,10 @@
* See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
* details about how to estimate network traffic.
*
+ * <p>
+ * Intents cannot be used for persisted JobWorkItems.
+ * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+ *
* @param intent The general Intent describing this work.
* @param downloadBytes The estimated size of network traffic that will be
* downloaded by this job work item, in bytes.
@@ -89,6 +114,7 @@
*/
public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes,
@BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) {
+ mExtras = PersistableBundle.EMPTY;
mIntent = intent;
mNetworkDownloadBytes = downloadBytes;
mNetworkUploadBytes = uploadBytes;
@@ -96,6 +122,25 @@
enforceValidity(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
}
+ private JobWorkItem(@NonNull Builder builder) {
+ mDeliveryCount = builder.mDeliveryCount;
+ mExtras = builder.mExtras.deepCopy();
+ mIntent = builder.mIntent;
+ mNetworkDownloadBytes = builder.mNetworkDownloadBytes;
+ mNetworkUploadBytes = builder.mNetworkUploadBytes;
+ mMinimumChunkBytes = builder.mMinimumNetworkChunkBytes;
+ }
+
+ /**
+ * Return the extras associated with this work.
+ *
+ * @see Builder#setExtras(PersistableBundle)
+ */
+ @NonNull
+ public PersistableBundle getExtras() {
+ return mExtras;
+ }
+
/**
* Return the Intent associated with this work.
*/
@@ -176,6 +221,7 @@
/**
* @hide
*/
+ @Nullable
public Object getGrants() {
return mGrants;
}
@@ -186,6 +232,8 @@
sb.append(mWorkId);
sb.append(" intent=");
sb.append(mIntent);
+ sb.append(" extras=");
+ sb.append(mExtras);
if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN) {
sb.append(" downloadBytes=");
sb.append(mNetworkDownloadBytes);
@@ -207,6 +255,140 @@
}
/**
+ * Builder class for constructing {@link JobWorkItem} objects.
+ */
+ public static final class Builder {
+ private int mDeliveryCount;
+ private PersistableBundle mExtras = PersistableBundle.EMPTY;
+ private Intent mIntent;
+ private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
+ private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
+ private long mMinimumNetworkChunkBytes = NETWORK_BYTES_UNKNOWN;
+
+ /**
+ * Initialize a new Builder to construct a {@link JobWorkItem} object.
+ */
+ public Builder() {
+ }
+
+ /**
+ * @see JobWorkItem#getDeliveryCount()
+ * @return This object for method chaining
+ * @hide
+ */
+ @NonNull
+ public Builder setDeliveryCount(int deliveryCount) {
+ mDeliveryCount = deliveryCount;
+ return this;
+ }
+
+ /**
+ * Set optional extras. This can be persisted, so we only allow primitive types.
+ * @param extras Bundle containing extras you want the scheduler to hold on to for you.
+ * @return This object for method chaining
+ * @see JobWorkItem#getExtras()
+ */
+ @NonNull
+ public Builder setExtras(@NonNull PersistableBundle extras) {
+ if (extras == null) {
+ throw new IllegalArgumentException("extras cannot be null");
+ }
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Set an intent with information relevant to this work item.
+ *
+ * <p>
+ * Intents cannot be used for persisted JobWorkItems.
+ * Use {@link #setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+ *
+ * @return This object for method chaining
+ * @see JobWorkItem#getIntent()
+ */
+ @NonNull
+ public Builder setIntent(@NonNull Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Set the estimated size of network traffic that will be performed for this work item,
+ * in bytes.
+ *
+ * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
+ * details about how to estimate network traffic.
+ *
+ * @param downloadBytes The estimated size of network traffic that will be
+ * downloaded for this work item, in bytes.
+ * @param uploadBytes The estimated size of network traffic that will be
+ * uploaded for this work item, in bytes.
+ * @return This object for method chaining
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ * @see JobWorkItem#getEstimatedNetworkDownloadBytes()
+ * @see JobWorkItem#getEstimatedNetworkUploadBytes()
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
+ @BytesLong long uploadBytes) {
+ if (downloadBytes != NETWORK_BYTES_UNKNOWN && downloadBytes < 0) {
+ throw new IllegalArgumentException(
+ "Invalid network download bytes: " + downloadBytes);
+ }
+ if (uploadBytes != NETWORK_BYTES_UNKNOWN && uploadBytes < 0) {
+ throw new IllegalArgumentException("Invalid network upload bytes: " + uploadBytes);
+ }
+ mNetworkDownloadBytes = downloadBytes;
+ mNetworkUploadBytes = uploadBytes;
+ return this;
+ }
+
+ /**
+ * Set the minimum size of non-resumable network traffic this work item requires, in bytes.
+ * When the upload or download can be easily paused and resumed, use this to set the
+ * smallest size that must be transmitted between start and stop events to be considered
+ * successful. If the transfer cannot be paused and resumed, then this should be the sum
+ * of the values provided to {@link #setEstimatedNetworkBytes(long, long)}.
+ *
+ * See {@link JobInfo.Builder#setMinimumNetworkChunkBytes(long)} for
+ * details about how to set the minimum chunk.
+ *
+ * @param chunkSizeBytes The smallest piece of data that cannot be easily paused and
+ * resumed, in bytes.
+ * @return This object for method chaining
+ * @see JobInfo.Builder#setMinimumNetworkChunkBytes(long)
+ * @see JobWorkItem#getMinimumNetworkChunkBytes()
+ * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long)
+ */
+ @NonNull
+ public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) {
+ if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) {
+ throw new IllegalArgumentException("Minimum chunk size must be positive");
+ }
+ mMinimumNetworkChunkBytes = chunkSizeBytes;
+ return this;
+ }
+
+ /**
+ * @return The JobWorkItem object to hand to the JobScheduler. This object is immutable.
+ */
+ @NonNull
+ public JobWorkItem build() {
+ return build(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
+ }
+
+ /** @hide */
+ @NonNull
+ public JobWorkItem build(boolean rejectNegativeNetworkEstimates) {
+ JobWorkItem jobWorkItem = new JobWorkItem(this);
+ jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
+ return jobWorkItem;
+ }
+ }
+
+ /**
* @hide
*/
public void enforceValidity(boolean rejectNegativeNetworkEstimates) {
@@ -249,6 +431,7 @@
} else {
out.writeInt(0);
}
+ out.writePersistableBundle(mExtras);
out.writeLong(mNetworkDownloadBytes);
out.writeLong(mNetworkUploadBytes);
out.writeLong(mMinimumChunkBytes);
@@ -274,6 +457,8 @@
} else {
mIntent = null;
}
+ final PersistableBundle extras = in.readPersistableBundle();
+ mExtras = extras != null ? extras : PersistableBundle.EMPTY;
mNetworkDownloadBytes = in.readLong();
mNetworkUploadBytes = in.readLong();
mMinimumChunkBytes = in.readLong();
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 c7a2997..8defa16 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1406,6 +1406,7 @@
if (toCancel.getJob().equals(job)) {
toCancel.enqueueWorkLocked(work);
+ mJobs.touchJob(toCancel);
// If any of work item is enqueued when the source is in the foreground,
// exempt the entire job.
@@ -3775,6 +3776,14 @@
}
}
}
+ if (job.isPersisted()) {
+ // Intent.saveToXml() doesn't persist everything, so just reject all
+ // JobWorkItems with Intents to be safe/predictable.
+ if (jobWorkItem.getIntent() != null) {
+ throw new IllegalArgumentException(
+ "Cannot persist JobWorkItems with Intents");
+ }
+ }
}
return JobScheduler.RESULT_SUCCESS;
}
@@ -3837,9 +3846,6 @@
final int userId = UserHandle.getUserId(uid);
enforceValidJobRequest(uid, job);
- if (job.isPersisted()) {
- throw new IllegalArgumentException("Can't enqueue work for persisted jobs");
- }
if (work == null) {
throw new NullPointerException("work is null");
}
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 285b982..ce7da86 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -615,6 +615,9 @@
"last work dequeued");
// This will finish the job.
doCallbackLocked(false, "last work dequeued");
+ } else {
+ // Delivery count has been updated, so persist JobWorkItem change.
+ mService.mJobs.touchJob(mRunningJob);
}
return work;
}
@@ -632,6 +635,7 @@
// Exception-throwing-can down the road to JobParameters.completeWork >:(
return true;
}
+ mService.mJobs.touchJob(mRunningJob);
return mRunningJob.completeWorkLocked(workId);
}
} finally {
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 5f5f447..88270494 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -25,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
+import android.app.job.JobWorkItem;
import android.content.ComponentName;
import android.content.Context;
import android.net.NetworkRequest;
@@ -310,6 +311,15 @@
mJobSet.removeJobsOfUnlistedUsers(keepUserIds);
}
+ /** Note a change in the specified JobStatus that necessitates writing job state to disk. */
+ void touchJob(@NonNull JobStatus jobStatus) {
+ if (!jobStatus.isPersisted()) {
+ return;
+ }
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
+ maybeWriteStatusToDiskAsync();
+ }
+
@VisibleForTesting
public void clear() {
mJobSet.clear();
@@ -430,6 +440,7 @@
private static final String XML_TAG_PERIODIC = "periodic";
private static final String XML_TAG_ONEOFF = "one-off";
private static final String XML_TAG_EXTRAS = "extras";
+ private static final String XML_TAG_JOB_WORK_ITEM = "job-work-item";
private void migrateJobFilesAsync() {
synchronized (mLock) {
@@ -724,6 +735,7 @@
writeConstraintsToXml(out, jobStatus);
writeExecutionCriteriaToXml(out, jobStatus);
writeBundleToXml(jobStatus.getJob().getExtras(), out);
+ writeJobWorkItemsToXml(out, jobStatus);
out.endTag(null, XML_TAG_JOB);
numJobs++;
@@ -906,6 +918,53 @@
out.endTag(null, XML_TAG_ONEOFF);
}
}
+
+ private void writeJobWorkItemsToXml(@NonNull TypedXmlSerializer out,
+ @NonNull JobStatus jobStatus) throws IOException, XmlPullParserException {
+ // Write executing first since they're technically at the front of the queue.
+ writeJobWorkItemListToXml(out, jobStatus.executingWork);
+ writeJobWorkItemListToXml(out, jobStatus.pendingWork);
+ }
+
+ private void writeJobWorkItemListToXml(@NonNull TypedXmlSerializer out,
+ @Nullable List<JobWorkItem> jobWorkItems)
+ throws IOException, XmlPullParserException {
+ if (jobWorkItems == null) {
+ return;
+ }
+ // Write the items in list order to maintain the enqueue order.
+ final int size = jobWorkItems.size();
+ for (int i = 0; i < size; ++i) {
+ final JobWorkItem item = jobWorkItems.get(i);
+ if (item.getGrants() != null) {
+ // We currently don't allow persisting jobs when grants are involved.
+ // TODO(256618122): allow persisting JobWorkItems with grant flags
+ continue;
+ }
+ if (item.getIntent() != null) {
+ // Intent.saveToXml() doesn't persist everything, so we shouldn't attempt to
+ // persist these JobWorkItems at all.
+ Slog.wtf(TAG, "Encountered JobWorkItem with Intent in persisting list");
+ continue;
+ }
+ out.startTag(null, XML_TAG_JOB_WORK_ITEM);
+ out.attributeInt(null, "delivery-count", item.getDeliveryCount());
+ if (item.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ out.attributeLong(null, "estimated-download-bytes",
+ item.getEstimatedNetworkDownloadBytes());
+ }
+ if (item.getEstimatedNetworkUploadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ out.attributeLong(null, "estimated-upload-bytes",
+ item.getEstimatedNetworkUploadBytes());
+ }
+ if (item.getMinimumNetworkChunkBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ out.attributeLong(null, "minimum-network-chunk-bytes",
+ item.getMinimumNetworkChunkBytes());
+ }
+ writeBundleToXml(item.getExtras(), out);
+ out.endTag(null, XML_TAG_JOB_WORK_ITEM);
+ }
+ }
};
/**
@@ -1262,7 +1321,13 @@
Slog.e(TAG, "Persisted extras contained invalid data", e);
return null;
}
- parser.nextTag(); // Consume </extras>
+ eventType = parser.nextTag(); // Consume </extras>
+
+ List<JobWorkItem> jobWorkItems = null;
+ if (eventType == XmlPullParser.START_TAG
+ && XML_TAG_JOB_WORK_ITEM.equals(parser.getName())) {
+ jobWorkItems = readJobWorkItemsFromXml(parser);
+ }
final JobInfo builtJob;
try {
@@ -1301,6 +1366,11 @@
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
(rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
+ if (jobWorkItems != null) {
+ for (int i = 0; i < jobWorkItems.size(); ++i) {
+ js.enqueueWorkLocked(jobWorkItems.get(i));
+ }
+ }
return js;
}
@@ -1473,6 +1543,64 @@
parser.getAttributeLong(null, "deadline", JobStatus.NO_LATEST_RUNTIME);
return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
}
+
+ @NonNull
+ private List<JobWorkItem> readJobWorkItemsFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ List<JobWorkItem> jobWorkItems = new ArrayList<>();
+
+ for (int eventType = parser.getEventType(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ final String tagName = parser.getName();
+ if (!XML_TAG_JOB_WORK_ITEM.equals(tagName)) {
+ // We're no longer operating with work items.
+ break;
+ }
+ try {
+ JobWorkItem jwi = readJobWorkItemFromXml(parser);
+ if (jwi != null) {
+ jobWorkItems.add(jwi);
+ }
+ } catch (Exception e) {
+ // If there's an issue with one JobWorkItem, drop only the one item and not the
+ // whole job.
+ Slog.e(TAG, "Problem with persisted JobWorkItem", e);
+ }
+ }
+
+ return jobWorkItems;
+ }
+
+ @Nullable
+ private JobWorkItem readJobWorkItemFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ JobWorkItem.Builder jwiBuilder = new JobWorkItem.Builder();
+
+ jwiBuilder
+ .setDeliveryCount(parser.getAttributeInt(null, "delivery-count"))
+ .setEstimatedNetworkBytes(
+ parser.getAttributeLong(null,
+ "estimated-download-bytes", JobInfo.NETWORK_BYTES_UNKNOWN),
+ parser.getAttributeLong(null,
+ "estimated-upload-bytes", JobInfo.NETWORK_BYTES_UNKNOWN))
+ .setMinimumNetworkChunkBytes(parser.getAttributeLong(null,
+ "minimum-network-chunk-bytes", JobInfo.NETWORK_BYTES_UNKNOWN));
+ parser.next();
+ try {
+ final PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
+ jwiBuilder.setExtras(extras);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Persisted extras contained invalid data", e);
+ return null;
+ }
+
+ try {
+ return jwiBuilder.build();
+ } catch (Exception e) {
+ Slog.e(TAG, "Invalid JobWorkItem", e);
+ return null;
+ }
+ }
}
/** Set of all tracked jobs. */
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 5712599..b0b6a01 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
@@ -676,6 +676,12 @@
Slog.i(TAG, "Cloning job with persisted run times", new RuntimeException("here"));
}
}
+ if (jobStatus.executingWork != null && jobStatus.executingWork.size() > 0) {
+ executingWork = new ArrayList<>(jobStatus.executingWork);
+ }
+ if (jobStatus.pendingWork != null && jobStatus.pendingWork.size() > 0) {
+ pendingWork = new ArrayList<>(jobStatus.pendingWork);
+ }
}
/**
diff --git a/core/api/current.txt b/core/api/current.txt
index a77040c..945531f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -935,6 +935,8 @@
field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
field public static final int keyboardLayout = 16843691; // 0x10103ab
+ field public static final int keyboardLayoutType;
+ field public static final int keyboardLocale;
field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
field public static final int keycode = 16842949; // 0x10100c5
@@ -5650,6 +5652,11 @@
field public static final int MODE_UNKNOWN = 0; // 0x0
}
+ public class GrammaticalInflectionManager {
+ method public int getApplicationGrammaticalGender();
+ method public void setRequestedApplicationGrammaticalGender(int);
+ }
+
public class Instrumentation {
ctor public Instrumentation();
method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
@@ -8588,12 +8595,22 @@
method public int getDeliveryCount();
method public long getEstimatedNetworkDownloadBytes();
method public long getEstimatedNetworkUploadBytes();
+ method @NonNull public android.os.PersistableBundle getExtras();
method public android.content.Intent getIntent();
method public long getMinimumNetworkChunkBytes();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
}
+ public static final class JobWorkItem.Builder {
+ ctor public JobWorkItem.Builder();
+ method @NonNull public android.app.job.JobWorkItem build();
+ method @NonNull public android.app.job.JobWorkItem.Builder setEstimatedNetworkBytes(long, long);
+ method @NonNull public android.app.job.JobWorkItem.Builder setExtras(@NonNull android.os.PersistableBundle);
+ method @NonNull public android.app.job.JobWorkItem.Builder setIntent(@NonNull android.content.Intent);
+ method @NonNull public android.app.job.JobWorkItem.Builder setMinimumNetworkChunkBytes(long);
+ }
+
}
package android.app.people {
@@ -10005,6 +10022,7 @@
field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
field public static final String FINGERPRINT_SERVICE = "fingerprint";
field public static final String GAME_SERVICE = "game";
+ field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final String HEALTHCONNECT_SERVICE = "healthconnect";
field public static final String INPUT_METHOD_SERVICE = "input_method";
@@ -10033,6 +10051,7 @@
field public static final String NFC_SERVICE = "nfc";
field public static final String NOTIFICATION_SERVICE = "notification";
field public static final String NSD_SERVICE = "servicediscovery";
+ field public static final String OVERLAY_SERVICE = "overlay";
field public static final String PEOPLE_SERVICE = "people";
field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
field public static final String POWER_SERVICE = "power";
@@ -11201,6 +11220,49 @@
}
+package android.content.om {
+
+ public class FabricatedOverlay {
+ ctor public FabricatedOverlay(@NonNull String, @NonNull String);
+ method @NonNull public android.content.om.OverlayIdentifier getIdentifier();
+ method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
+ method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
+ method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
+ method public void setTargetOverlayable(@Nullable String);
+ }
+
+ public final class OverlayIdentifier implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayIdentifier> CREATOR;
+ }
+
+ public final class OverlayInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.content.om.OverlayIdentifier getOverlayIdentifier();
+ method @Nullable public String getOverlayName();
+ method @Nullable public String getTargetOverlayableName();
+ method @NonNull public String getTargetPackageName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
+ }
+
+ public class OverlayManager {
+ method @NonNull @NonUiContext public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@NonNull String);
+ }
+
+ public final class OverlayManagerTransaction implements android.os.Parcelable {
+ ctor public OverlayManagerTransaction(@NonNull android.content.om.OverlayManager);
+ method @NonUiContext public void commit() throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method public int describeContents();
+ method @NonNull public void registerFabricatedOverlay(@NonNull android.content.om.FabricatedOverlay);
+ method @NonNull public void unregisterFabricatedOverlay(@NonNull android.content.om.OverlayIdentifier);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayManagerTransaction> CREATOR;
+ }
+
+}
+
package android.content.pm {
public class ActivityInfo extends android.content.pm.ComponentInfo implements android.os.Parcelable {
@@ -11217,6 +11279,7 @@
field public static final int CONFIG_DENSITY = 4096; // 0x1000
field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
field public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 268435456; // 0x10000000
+ field public static final int CONFIG_GRAMMATICAL_GENDER = 32768; // 0x8000
field public static final int CONFIG_KEYBOARD = 16; // 0x10
field public static final int CONFIG_KEYBOARD_HIDDEN = 32; // 0x20
field public static final int CONFIG_LAYOUT_DIRECTION = 8192; // 0x2000
@@ -11765,6 +11828,7 @@
method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
+ method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, int, @NonNull android.content.IntentSender);
method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public void uninstallExistingPackage(@NonNull String, @Nullable android.content.IntentSender);
method public void unregisterSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
method public void updateSessionAppIcon(int, @Nullable android.graphics.Bitmap);
@@ -11893,6 +11957,8 @@
method public int getInstallReason();
method @Nullable public String getInstallerAttributionTag();
method @Nullable public String getInstallerPackageName();
+ method public int getInstallerUid();
+ method @NonNull public boolean getIsPreApprovalRequested();
method public int getMode();
method public int getOriginatingUid();
method @Nullable public android.net.Uri getOriginatingUri();
@@ -11943,6 +12009,7 @@
method public void setInstallLocation(int);
method public void setInstallReason(int);
method public void setInstallScenario(int);
+ method public void setInstallerPackageName(@Nullable String);
method public void setKeepApplicationEnabledSetting();
method public void setMultiPackage();
method public void setOriginatingUid(int);
@@ -12203,6 +12270,7 @@
field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS = "android.hardware.identity_credential_direct_access";
field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
+ field public static final String FEATURE_IPSEC_TUNNEL_MIGRATION = "android.software.ipsec_tunnel_migration";
field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key";
field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
@@ -12831,6 +12899,7 @@
method public int diff(android.content.res.Configuration);
method public boolean equals(android.content.res.Configuration);
method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration);
+ method public int getGrammaticalGender();
method public int getLayoutDirection();
method @NonNull public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
@@ -12860,6 +12929,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
field public static final int FONT_WEIGHT_ADJUSTMENT_UNDEFINED = 2147483647; // 0x7fffffff
+ field public static final int GRAMMATICAL_GENDER_FEMININE = 3; // 0x3
+ field public static final int GRAMMATICAL_GENDER_MASCULINE = 4; // 0x4
+ field public static final int GRAMMATICAL_GENDER_NEUTRAL = 2; // 0x2
+ field public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0; // 0x0
field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
field public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; // 0x0
field public static final int HARDKEYBOARDHIDDEN_YES = 2; // 0x2
@@ -13107,6 +13180,7 @@
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromDirectory(@NonNull String, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadOverlay(@NonNull android.content.om.OverlayInfo) throws java.io.IOException;
}
}
@@ -14881,6 +14955,8 @@
enum_constant public static final android.graphics.ColorSpace.Named ACESCG;
enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB;
enum_constant public static final android.graphics.ColorSpace.Named BT2020;
+ enum_constant public static final android.graphics.ColorSpace.Named BT2020_HLG;
+ enum_constant public static final android.graphics.ColorSpace.Named BT2020_PQ;
enum_constant public static final android.graphics.ColorSpace.Named BT709;
enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB;
enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ;
@@ -19917,8 +19993,9 @@
method public int describeContents();
method @NonNull public android.location.GnssClock getClock();
method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls();
- method public boolean getIsFullTracking();
method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements();
+ method public boolean hasFullTracking();
+ method public boolean isFullTracking();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR;
}
@@ -19927,9 +20004,10 @@
ctor public GnssMeasurementsEvent.Builder();
ctor public GnssMeasurementsEvent.Builder(@NonNull android.location.GnssMeasurementsEvent);
method @NonNull public android.location.GnssMeasurementsEvent build();
+ method @NonNull public android.location.GnssMeasurementsEvent.Builder clearFullTracking();
method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock);
+ method @NonNull public android.location.GnssMeasurementsEvent.Builder setFullTracking(boolean);
method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>);
- method @NonNull public android.location.GnssMeasurementsEvent.Builder setIsFullTracking(boolean);
method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>);
}
@@ -25552,6 +25630,7 @@
public abstract static class MediaProjection.Callback {
ctor public MediaProjection.Callback();
method public void onCapturedContentResize(int, int);
+ method public void onCapturedContentVisibilityChanged(boolean);
method public void onStop();
}
@@ -41088,6 +41167,7 @@
field public static final int ROUTE_BLUETOOTH = 2; // 0x2
field public static final int ROUTE_EARPIECE = 1; // 0x1
field public static final int ROUTE_SPEAKER = 8; // 0x8
+ field public static final int ROUTE_STREAMING = 16; // 0x10
field public static final int ROUTE_WIRED_HEADSET = 4; // 0x4
field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
}
@@ -41099,6 +41179,7 @@
method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+ method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
}
public final class CallEndpoint implements android.os.Parcelable {
@@ -41134,6 +41215,8 @@
public interface CallEventCallback {
method public void onAnswer(int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState);
+ method public void onCallStreamingFailed(int);
+ method public void onCallStreamingStarted(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onDisconnect(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onReject(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onSetActive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -41199,6 +41282,18 @@
method public android.telecom.CallScreeningService.CallResponse.Builder setSkipNotification(boolean);
}
+ public abstract class CallStreamingService extends android.app.Service {
+ ctor public CallStreamingService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onCallStreamingStarted(@NonNull android.telecom.StreamingCall);
+ method public void onCallStreamingStateChanged(int);
+ method public void onCallStreamingStopped();
+ field public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+ field public static final int STREAMING_FAILED_ALREADY_STREAMING = 1; // 0x1
+ field public static final int STREAMING_FAILED_NO_SENDER = 2; // 0x2
+ field public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3; // 0x3
+ }
+
public abstract class Conference extends android.telecom.Conferenceable {
ctor public Conference(android.telecom.PhoneAccountHandle);
method public final boolean addConnection(android.telecom.Connection);
@@ -41664,6 +41759,7 @@
field public static final int CAPABILITY_RTT = 4096; // 0x1000
field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+ field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 524288; // 0x80000
field public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 262144; // 0x40000
field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
field public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 65536; // 0x10000
@@ -41853,6 +41949,22 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR;
}
+ public final class StreamingCall implements android.os.Parcelable {
+ ctor public StreamingCall(@NonNull android.content.ComponentName, @NonNull String, @NonNull android.net.Uri, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public android.net.Uri getAddress();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @NonNull public String getDisplayName();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getState();
+ method public void setStreamingState(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
+ field public static final int STATE_DISCONNECTED = 3; // 0x3
+ field public static final int STATE_HOLDING = 2; // 0x2
+ field public static final int STATE_STREAMING = 1; // 0x1
+ }
+
public class TelecomManager {
method public void acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 97b9a51..c09c6c5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1496,11 +1496,13 @@
method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getDataManagementIntent(String);
method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public CharSequence getDataManagementIntentLabel(@NonNull String);
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public String getDataManagementLabel(@NonNull String);
+ method @NonNull public android.app.backup.BackupRestoreEventLogger getDelayedRestoreLogger();
method @RequiresPermission(android.Manifest.permission.BACKUP) public String getDestinationString(String);
method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isAppEligibleForBackup(String);
method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupEnabled();
method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupServiceActive(android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.BACKUP) public String[] listAllTransports();
+ method @NonNull public void reportDelayedRestoreResult(@NonNull android.app.backup.BackupRestoreEventLogger);
method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver);
method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int);
method @Deprecated public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor);
@@ -3331,6 +3333,7 @@
field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
field public static final String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE";
field public static final String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET";
+ field public static final String EXTRA_INSTALL_RESULT = "android.intent.extra.INSTALL_RESULT";
field public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION";
field public static final String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES";
field public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS";
@@ -3346,6 +3349,7 @@
field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION";
+ field public static final String EXTRA_UNINSTALL_ALL_USERS = "android.intent.extra.UNINSTALL_ALL_USERS";
field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
@@ -3429,15 +3433,10 @@
package android.content.om {
public final class OverlayInfo implements android.os.Parcelable {
- method public int describeContents();
method @Nullable public String getCategory();
method @NonNull public String getPackageName();
- method @Nullable public String getTargetOverlayableName();
- method @NonNull public String getTargetPackageName();
method public int getUserId();
method public boolean isEnabled();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
}
public class OverlayManager {
@@ -3452,9 +3451,11 @@
package android.content.pm {
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+ method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public boolean hasFragileUserData();
method public boolean isEncryptionAware();
method public boolean isInstantApp();
method public boolean isOem();
+ method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public boolean isPrivilegedApp();
method public boolean isProduct();
method public boolean isVendor();
field public String credentialProtectedDataDir;
@@ -3572,16 +3573,32 @@
}
public class PackageInstaller {
+ method @NonNull public android.content.pm.PackageInstaller.InstallInfo getInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
+ field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
+ field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
field public static final int DATA_LOADER_TYPE_NONE = 0; // 0x0
field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1
+ field public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
+ field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
+ field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
field public static final int LOCATION_DATA_APP = 0; // 0x0
field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
}
+ public static class PackageInstaller.InstallInfo {
+ method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
+ method public int getInstallLocation();
+ method @NonNull public String getPackageName();
+ }
+
+ public static class PackageInstaller.PackageParsingException extends java.lang.Exception {
+ method public int getErrorCode();
+ }
+
public static class PackageInstaller.Session implements java.io.Closeable {
method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
@@ -3628,6 +3645,7 @@
public abstract class PackageManager {
method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract boolean arePermissionsIndividuallyControlled();
+ method @NonNull public boolean canUserUninstall(@NonNull String, @NonNull android.os.UserHandle);
method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
method @NonNull @RequiresPermission("android.permission.GET_APP_METADATA") public android.os.PersistableBundle getAppMetadata(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -3646,6 +3664,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -3673,11 +3692,19 @@
method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
method public void setSystemAppState(@NonNull String, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
+ method @NonNull public boolean shouldShowNewAppInstalledNotification();
method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+ field public static final int DELETE_ALL_USERS = 2; // 0x2
+ field public static final int DELETE_FAILED_ABORTED = -5; // 0xfffffffb
+ field public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; // 0xfffffffe
+ field public static final int DELETE_FAILED_INTERNAL_ERROR = -1; // 0xffffffff
+ field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
+ field public static final int DELETE_KEEP_DATA = 1; // 0x1
+ field public static final int DELETE_SUCCEEDED = 1; // 0x1
field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -3785,6 +3812,11 @@
@IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
}
+ public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+ method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
+ }
+
public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
field @StringRes public final int backgroundRequestDetailResourceId;
field @StringRes public final int backgroundRequestResourceId;
@@ -10928,6 +10960,7 @@
field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
+ field public static final String ACTION_USER_SETTINGS = "android.settings.USER_SETTINGS";
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 42acc22..233dee9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -814,7 +814,7 @@
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
method public boolean hasRequestForegroundServiceExemption();
method public boolean isOnBackInvokedCallbackEnabled();
- method public boolean isPrivilegedApp();
+ method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public boolean isPrivilegedApp();
method public boolean isSystemApp();
method public void setEnableOnBackInvokedCallback(boolean);
field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
@@ -831,7 +831,6 @@
public static class PackageInstaller.SessionParams implements android.os.Parcelable {
method public void setInstallFlagAllowTest();
- method public void setInstallerPackageName(@Nullable String);
}
public abstract class PackageManager {
@@ -1299,6 +1298,7 @@
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+ method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
@@ -3256,6 +3256,7 @@
method public int getDisplayId();
method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
+ method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index ce99119..558dae5 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -59,6 +59,15 @@
}
}
+ /** Reports {@link android.app.servertransaction.RefreshCallbackItem} is executed. */
+ public void activityRefreshed(IBinder token) {
+ try {
+ getActivityClientController().activityRefreshed(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Reports after {@link Activity#onTopResumedActivityChanged(boolean)} is called for losing the
* top most position.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 37749e6..8e747b2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -62,6 +62,7 @@
import android.app.servertransaction.TransactionExecutor;
import android.app.servertransaction.TransactionExecutorHelper;
import android.bluetooth.BluetoothFrameworkInitializer;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AutofillOptions;
@@ -361,6 +362,8 @@
private int mLastProcessState = PROCESS_STATE_UNKNOWN;
ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
private int mLastSessionId;
+ // Holds the value of the last reported device ID value from the server for the top activity.
+ int mLastReportedDeviceId;
final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
@UnsupportedAppUsage
final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -546,6 +549,9 @@
boolean hideForNow;
Configuration createdConfig;
Configuration overrideConfig;
+ // TODO(b/263402465): pass deviceId directly in LaunchActivityItem#execute
+ // The deviceId assigned by the server when this activity was first started.
+ int mDeviceId;
// Used for consolidating configs before sending on to Activity.
private Configuration tmpConfig = new Configuration();
// Callback used for updating activity override config and camera compat control state.
@@ -608,7 +614,7 @@
}
public ActivityClientRecord(IBinder token, Intent intent, int ident,
- ActivityInfo info, Configuration overrideConfig,
+ ActivityInfo info, Configuration overrideConfig, int deviceId,
String referrer, IVoiceInteractor voiceInteractor, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -630,6 +636,7 @@
this.isForward = isForward;
this.profilerInfo = profilerInfo;
this.overrideConfig = overrideConfig;
+ this.mDeviceId = deviceId;
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
mActivityOptions = activityOptions;
mLaunchedFromBubble = launchedFromBubble;
@@ -3816,6 +3823,7 @@
// Make sure we are running with the most recent config.
mConfigurationController.handleConfigurationChanged(null, null);
+ updateDeviceIdForNonUIContexts(r.mDeviceId);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
@@ -4548,6 +4556,9 @@
context.setOuterContext(service);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
+ if (!service.isUiContext()) { // WindowProviderService is a UI Context.
+ service.updateDeviceId(mLastReportedDeviceId);
+ }
service.onCreate();
mServicesData.put(data.token, data);
mServices.put(data.token, service);
@@ -5342,6 +5353,11 @@
}
}
+ @Override
+ public void reportRefresh(ActivityClientRecord r) {
+ ActivityClient.getInstance().activityRefreshed(r.token);
+ }
+
private void handleSetCoreSettings(Bundle coreSettings) {
synchronized (mCoreSettingsLock) {
mCoreSettings = coreSettings;
@@ -6061,9 +6077,48 @@
}
}
+ private void updateDeviceIdForNonUIContexts(int deviceId) {
+ // Invalid device id is treated as a no-op.
+ if (deviceId == VirtualDeviceManager.DEVICE_ID_INVALID) {
+ return;
+ }
+ if (deviceId == mLastReportedDeviceId) {
+ return;
+ }
+ mLastReportedDeviceId = deviceId;
+ ArrayList<Context> nonUIContexts = new ArrayList<>();
+ // Update Application and Service contexts with implicit device association.
+ // UI Contexts are able to derived their device Id association from the display.
+ synchronized (mResourcesManager) {
+ final int numApps = mAllApplications.size();
+ for (int i = 0; i < numApps; i++) {
+ nonUIContexts.add(mAllApplications.get(i));
+ }
+ final int numServices = mServices.size();
+ for (int i = 0; i < numServices; i++) {
+ final Service service = mServices.valueAt(i);
+ // WindowProviderService is a UI Context.
+ if (!service.isUiContext()) {
+ nonUIContexts.add(service);
+ }
+ }
+ }
+ for (Context context : nonUIContexts) {
+ try {
+ context.updateDeviceId(deviceId);
+ } catch (IllegalArgumentException e) {
+ // It can happen that the system already closed/removed a virtual device
+ // and the passed deviceId is no longer valid.
+ // TODO(b/263355088): check for validity of deviceId before updating
+ // instead of catching this exception once VDM add an API to validate ids.
+ }
+ }
+ }
+
@Override
- public void handleConfigurationChanged(Configuration config) {
+ public void handleConfigurationChanged(Configuration config, int deviceId) {
mConfigurationController.handleConfigurationChanged(config);
+ updateDeviceIdForNonUIContexts(deviceId);
// These are only done to maintain @UnsupportedAppUsage and should be removed someday.
mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 309b253..a7a4b35 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3875,4 +3875,19 @@
throw e.rethrowAsRuntimeException();
}
}
+
+ @Override
+ public boolean canUserUninstall(String packageName, UserHandle user) {
+ try {
+ return mPM.getBlockUninstallForUser(packageName, user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public boolean shouldShowNewAppInstalledNotification() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, 0) == 1;
+ }
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f322ca9..3ba5783 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -139,6 +139,9 @@
/** Restart the activity after it was stopped. */
public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start);
+ /** Report that activity was refreshed to server. */
+ public abstract void reportRefresh(@NonNull ActivityClientRecord r);
+
/** Set pending activity configuration in case it will be updated by other transaction item. */
public abstract void updatePendingActivityConfiguration(@NonNull IBinder token,
Configuration overrideConfig);
@@ -181,8 +184,8 @@
/** Get package info. */
public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai);
- /** Deliver app configuration change notification. */
- public abstract void handleConfigurationChanged(Configuration config);
+ /** Deliver app configuration change notification and device association. */
+ public abstract void handleConfigurationChanged(Configuration config, int deviceId);
/**
* Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a832b9a..1120257 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2710,7 +2710,7 @@
context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(),
mResources.getLoaders()));
- context.mDisplay = display;
+ context.setDisplay(display);
// Inherit context type if the container is from System or System UI context to bypass
// UI context check.
context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
@@ -2726,6 +2726,13 @@
return context;
}
+ private void setDisplay(Display display) {
+ mDisplay = display;
+ if (display != null) {
+ updateDeviceIdIfChanged(display.getDisplayId());
+ }
+ }
+
@Override
public @NonNull Context createDeviceContext(int deviceId) {
if (!isValidDeviceId(deviceId)) {
@@ -2863,8 +2870,8 @@
baseContext.setResources(windowContextResources);
// Associate the display with window context resources so that configuration update from
// the server side will also apply to the display's metrics.
- baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
- windowContextResources);
+ baseContext.setDisplay(ResourcesManager.getInstance().getAdjustedDisplay(
+ displayId, windowContextResources));
return baseContext;
}
@@ -3008,11 +3015,24 @@
@Override
public void updateDisplay(int displayId) {
- mDisplay = mResourcesManager.getAdjustedDisplay(displayId, mResources);
+ setDisplay(mResourcesManager.getAdjustedDisplay(displayId, mResources));
if (mContextType == CONTEXT_TYPE_NON_UI) {
mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT;
}
- // TODO(b/253201821): Update deviceId when display is updated.
+ }
+
+ private void updateDeviceIdIfChanged(int displayId) {
+ if (mIsExplicitDeviceId) {
+ return;
+ }
+ VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
+ if (vdm != null) {
+ int deviceId = vdm.getDeviceIdForDisplayId(displayId);
+ if (deviceId != mDeviceId) {
+ mDeviceId = deviceId;
+ notifyOnDeviceChangedListeners(mDeviceId);
+ }
+ }
}
@Override
@@ -3307,8 +3327,8 @@
classLoader,
packageInfo.getApplication() == null ? null
: packageInfo.getApplication().getResources().getLoaders()));
- context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
- context.getResources());
+ context.setDisplay(resourcesManager.getAdjustedDisplay(
+ displayId, context.getResources()));
return context;
}
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
new file mode 100644
index 0000000..1905b6a
--- /dev/null
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class allow applications to control granular grammatical inflection settings (such as
+ * per-app grammatical gender).
+ */
+@SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
+public class GrammaticalInflectionManager {
+ private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+ Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ Configuration.GRAMMATICAL_GENDER_NEUTRAL,
+ Configuration.GRAMMATICAL_GENDER_FEMININE,
+ Configuration.GRAMMATICAL_GENDER_MASCULINE));
+
+ private final Context mContext;
+ private final IGrammaticalInflectionManager mService;
+
+ /** @hide Instantiated by ContextImpl */
+ public GrammaticalInflectionManager(Context context, IGrammaticalInflectionManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns the current grammatical gender for the calling app. A new value can be requested via
+ * {@link #setRequestedApplicationGrammaticalGender(int)} and will be updated with a new
+ * configuration change. The method always returns the value received with the last received
+ * configuration change.
+ *
+ * @return the value of grammatical gender
+ * @see Configuration#getGrammaticalGender
+ */
+ @Configuration.GrammaticalGender
+ public int getApplicationGrammaticalGender() {
+ return mContext.getApplicationContext()
+ .getResources()
+ .getConfiguration()
+ .getGrammaticalGender();
+ }
+
+ /**
+ * Sets the current grammatical gender for the calling app (keyed by package name and user ID
+ * retrieved from the calling pid).
+ *
+ * <p><b>Note:</b> Changes to app grammatical gender will result in a configuration change (and
+ * potentially an Activity re-creation) being applied to the specified application. For more
+ * information, see the <a
+ * href="https://developer.android.com/guide/topics/resources/runtime-changes">section on
+ * handling configuration changes</a>. The set grammatical gender are persisted across
+ * application restarts; they are backed up if the user has enabled Backup & Restore.`
+ *
+ * @param grammaticalGender the terms of address the user preferred in an application.
+ * @see Configuration#getGrammaticalGender
+ */
+ public void setRequestedApplicationGrammaticalGender(
+ @Configuration.GrammaticalGender int grammaticalGender) {
+ if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+ throw new IllegalArgumentException("Unknown grammatical gender");
+ }
+
+ try {
+ mService.setRequestedApplicationGrammaticalGender(
+ mContext.getPackageName(), mContext.getUserId(), grammaticalGender);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 286b84c..ecea46a 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -39,6 +39,7 @@
interface IActivityClientController {
oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
+ oneway void activityRefreshed(in IBinder token);
/**
* This call is not one-way because {@link #activityPaused()) is not one-way, or
* the top-resumed-lost could be reported after activity paused.
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
new file mode 100644
index 0000000..9366a45
--- /dev/null
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -0,0 +1,19 @@
+package android.app;
+
+
+/**
+ * Internal interface used to control app-specific gender.
+ *
+ * <p>Use the {@link android.app.GrammarInflectionManager} class rather than going through
+ * this Binder interface directly. See {@link android.app.GrammarInflectionManager} for
+ * more complete documentation.
+ *
+ * @hide
+ */
+ interface IGrammaticalInflectionManager {
+
+ /**
+ * Sets a specified app’s app-specific grammatical gender.
+ */
+ void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
+ }
\ No newline at end of file
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index df13a87..5b3b2a6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1547,6 +1547,7 @@
IAmbientContextManager.Stub.asInterface(iBinder);
return new AmbientContextManager(ctx.getOuterContext(), manager);
}});
+
registerService(Context.WEARABLE_SENSING_SERVICE, WearableSensingManager.class,
new CachedServiceFetcher<WearableSensingManager>() {
@Override
@@ -1559,6 +1560,18 @@
return new WearableSensingManager(ctx.getOuterContext(), manager);
}});
+ registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
+ new CachedServiceFetcher<GrammaticalInflectionManager>() {
+ @Override
+ public GrammaticalInflectionManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new GrammaticalInflectionManager(ctx,
+ IGrammaticalInflectionManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(
+ Context.GRAMMATICAL_INFLECTION_SERVICE)));
+ }});
+
+
sInitializing = true;
try {
// Note: the following functions need to be @SystemApis, once they become mainline
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 7255c3e..bad282e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -1041,6 +1042,42 @@
return backupAgent.getBackupRestoreEventLogger();
}
+ /**
+ * Get an instance of {@link BackupRestoreEventLogger} to report B&R related events during a
+ * delayed restore operation.
+ *
+ * @return an instance of {@link BackupRestoreEventLogger}.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public BackupRestoreEventLogger getDelayedRestoreLogger() {
+ return new BackupRestoreEventLogger(OperationType.RESTORE);
+ }
+
+ /**
+ * Report B&R related events following a delayed restore operation.
+ *
+ * @param logger an instance of {@link BackupRestoreEventLogger} to which the corresponding
+ * events have been logged.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public void reportDelayedRestoreResult(@NonNull BackupRestoreEventLogger logger) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.reportDelayedRestoreResult(mContext.getPackageName(),
+ logger.getLoggingResults());
+ } catch (RemoteException e) {
+ Log.w(TAG, "reportDelayedRestoreResult() couldn't connect");
+ }
+ }
+ }
+
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index bf5be95..aeb4987 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupObserver;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IFullBackupRestoreObserver;
@@ -722,4 +723,6 @@
* that have been excluded will be passed to the agent to make it aware of the exclusions.
*/
void excludeKeysFromRestore(String packageName, in List<String> keys);
+
+ void reportDelayedRestoreResult(in String packageName, in List<DataTypeResult> results);
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index d94f08b..b159f33 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -38,6 +38,9 @@
return UNDEFINED;
}
+ boolean shouldHaveDefinedPreExecutionState() {
+ return true;
+ }
// Parcelable
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 49a1c66..a563bbc 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -32,6 +32,7 @@
public class ConfigurationChangeItem extends ClientTransactionItem {
private Configuration mConfiguration;
+ private int mDeviceId;
@Override
public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
@@ -42,7 +43,7 @@
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
- client.handleConfigurationChanged(mConfiguration);
+ client.handleConfigurationChanged(mConfiguration, mDeviceId);
}
@@ -51,12 +52,13 @@
private ConfigurationChangeItem() {}
/** Obtain an instance initialized with provided params. */
- public static ConfigurationChangeItem obtain(Configuration config) {
+ public static ConfigurationChangeItem obtain(Configuration config, int deviceId) {
ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class);
if (instance == null) {
instance = new ConfigurationChangeItem();
}
instance.mConfiguration = config;
+ instance.mDeviceId = deviceId;
return instance;
}
@@ -64,6 +66,7 @@
@Override
public void recycle() {
mConfiguration = null;
+ mDeviceId = 0;
ObjectPool.recycle(this);
}
@@ -74,11 +77,13 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedObject(mConfiguration, flags);
+ dest.writeInt(mDeviceId);
}
/** Read from Parcel. */
private ConfigurationChangeItem(Parcel in) {
mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ mDeviceId = in.readInt();
}
public static final @android.annotation.NonNull Creator<ConfigurationChangeItem> CREATOR =
@@ -101,16 +106,20 @@
return false;
}
final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
- return Objects.equals(mConfiguration, other.mConfiguration);
+ return Objects.equals(mConfiguration, other.mConfiguration)
+ && mDeviceId == other.mDeviceId;
}
@Override
public int hashCode() {
- return mConfiguration.hashCode();
+ int result = 17;
+ result = 31 * result + mDeviceId;
+ result = 31 * result + mConfiguration.hashCode();
+ return result;
}
@Override
public String toString() {
- return "ConfigurationChangeItem{config=" + mConfiguration + "}";
+ return "ConfigurationChangeItem{deviceId=" + mDeviceId + ", config" + mConfiguration + "}";
}
}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 7e4db81..3d0aa25 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -58,6 +58,7 @@
private ActivityInfo mInfo;
private Configuration mCurConfig;
private Configuration mOverrideConfig;
+ private int mDeviceId;
private String mReferrer;
private IVoiceInteractor mVoiceInteractor;
private int mProcState;
@@ -95,7 +96,7 @@
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
- mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
+ mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
mTaskFragmentToken);
@@ -116,7 +117,7 @@
/** Obtain an instance initialized with provided params. */
public static LaunchActivityItem obtain(Intent intent, int ident, ActivityInfo info,
- Configuration curConfig, Configuration overrideConfig,
+ Configuration curConfig, Configuration overrideConfig, int deviceId,
String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -127,7 +128,7 @@
if (instance == null) {
instance = new LaunchActivityItem();
}
- setValues(instance, intent, ident, info, curConfig, overrideConfig, referrer,
+ setValues(instance, intent, ident, info, curConfig, overrideConfig, deviceId, referrer,
voiceInteractor, procState, state, persistentState, pendingResults,
pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
activityClientController, shareableActivityToken,
@@ -138,7 +139,7 @@
@Override
public void recycle() {
- setValues(this, null, 0, null, null, null, null, null, 0, null, null, null, null,
+ setValues(this, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
null, false, null, null, null, null, false, null);
ObjectPool.recycle(this);
}
@@ -154,6 +155,7 @@
dest.writeTypedObject(mInfo, flags);
dest.writeTypedObject(mCurConfig, flags);
dest.writeTypedObject(mOverrideConfig, flags);
+ dest.writeInt(mDeviceId);
dest.writeString(mReferrer);
dest.writeStrongInterface(mVoiceInteractor);
dest.writeInt(mProcState);
@@ -175,7 +177,7 @@
private LaunchActivityItem(Parcel in) {
setValues(this, in.readTypedObject(Intent.CREATOR), in.readInt(),
in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
- in.readTypedObject(Configuration.CREATOR), in.readString(),
+ in.readTypedObject(Configuration.CREATOR), in.readInt(), in.readString(),
IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
in.readBundle(getClass().getClassLoader()),
in.readPersistableBundle(getClass().getClassLoader()),
@@ -215,6 +217,7 @@
return intentsEqual && mIdent == other.mIdent
&& activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
&& Objects.equals(mOverrideConfig, other.mOverrideConfig)
+ && mDeviceId == other.mDeviceId
&& Objects.equals(mReferrer, other.mReferrer)
&& mProcState == other.mProcState && areBundlesEqualRoughly(mState, other.mState)
&& areBundlesEqualRoughly(mPersistentState, other.mPersistentState)
@@ -235,6 +238,7 @@
result = 31 * result + mIdent;
result = 31 * result + Objects.hashCode(mCurConfig);
result = 31 * result + Objects.hashCode(mOverrideConfig);
+ result = 31 * result + mDeviceId;
result = 31 * result + Objects.hashCode(mReferrer);
result = 31 * result + Objects.hashCode(mProcState);
result = 31 * result + getRoughBundleHashCode(mState);
@@ -279,16 +283,17 @@
public String toString() {
return "LaunchActivityItem{intent=" + mIntent + ",ident=" + mIdent + ",info=" + mInfo
+ ",curConfig=" + mCurConfig + ",overrideConfig=" + mOverrideConfig
- + ",referrer=" + mReferrer + ",procState=" + mProcState + ",state=" + mState
- + ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
- + ",pendingNewIntents=" + mPendingNewIntents + ",options=" + mActivityOptions
- + ",profilerInfo=" + mProfilerInfo + ",assistToken=" + mAssistToken
- + ",shareableActivityToken=" + mShareableActivityToken + "}";
+ + ",deviceId=" + mDeviceId + ",referrer=" + mReferrer + ",procState=" + mProcState
+ + ",state=" + mState + ",persistentState=" + mPersistentState
+ + ",pendingResults=" + mPendingResults + ",pendingNewIntents=" + mPendingNewIntents
+ + ",options=" + mActivityOptions + ",profilerInfo=" + mProfilerInfo
+ + ",assistToken=" + mAssistToken + ",shareableActivityToken="
+ + mShareableActivityToken + "}";
}
// Using the same method to set and clear values to make sure we don't forget anything
private static void setValues(LaunchActivityItem instance, Intent intent, int ident,
- ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+ ActivityInfo info, Configuration curConfig, Configuration overrideConfig, int deviceId,
String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
@@ -300,6 +305,7 @@
instance.mInfo = info;
instance.mCurConfig = curConfig;
instance.mOverrideConfig = overrideConfig;
+ instance.mDeviceId = deviceId;
instance.mReferrer = referrer;
instance.mVoiceInteractor = voiceInteractor;
instance.mProcState = procState;
diff --git a/core/java/android/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java
new file mode 100644
index 0000000..74abab2
--- /dev/null
+++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Callback that allows to {@link TransactionExecutor#cycleToPath} to {@link ON_PAUSE} or
+ * {@link ON_STOP} in {@link TransactionExecutor#executeCallbacks} for activity "refresh" flow
+ * that goes through "paused -> resumed" or "stopped -> resumed" cycle.
+ *
+ * <p>This is used in combination with {@link com.android.server.wm.DisplayRotationCompatPolicy}
+ * for camera compatibility treatment that handles orientation mismatch between camera buffers and
+ * an app window. This allows to clear cached values in apps (e.g. display or camera rotation) that
+ * influence camera preview and can lead to sideways or stretching issues.
+ *
+ * @hide
+ */
+public class RefreshCallbackItem extends ActivityTransactionItem {
+
+ // Whether refresh should happen using the "stopped -> resumed" cycle or
+ // "paused -> resumed" cycle.
+ @LifecycleState
+ private int mPostExecutionState;
+
+ @Override
+ public void execute(@NonNull ClientTransactionHandler client,
+ @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions) {}
+
+ @Override
+ public void postExecute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ final ActivityClientRecord r = getActivityClientRecord(client, token);
+ client.reportRefresh(r);
+ }
+
+ @Override
+ public int getPostExecutionState() {
+ return mPostExecutionState;
+ }
+
+ @Override
+ boolean shouldHaveDefinedPreExecutionState() {
+ return false;
+ }
+
+ // ObjectPoolItem implementation
+
+ @Override
+ public void recycle() {
+ ObjectPool.recycle(this);
+ }
+
+ /**
+ * Obtain an instance initialized with provided params.
+ * @param postExecutionState indicating whether refresh should happen using the
+ * "stopped -> resumed" cycle or "paused -> resumed" cycle.
+ */
+ public static RefreshCallbackItem obtain(@LifecycleState int postExecutionState) {
+ if (postExecutionState != ON_STOP && postExecutionState != ON_PAUSE) {
+ throw new IllegalArgumentException(
+ "Only ON_STOP or ON_PAUSE are allowed as a post execution state for "
+ + "RefreshCallbackItem but got " + postExecutionState);
+ }
+ RefreshCallbackItem instance =
+ ObjectPool.obtain(RefreshCallbackItem.class);
+ if (instance == null) {
+ instance = new RefreshCallbackItem();
+ }
+ instance.mPostExecutionState = postExecutionState;
+ return instance;
+ }
+
+ private RefreshCallbackItem() {}
+
+ // Parcelable implementation
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mPostExecutionState);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final RefreshCallbackItem other = (RefreshCallbackItem) o;
+ return mPostExecutionState == other.mPostExecutionState;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mPostExecutionState;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "RefreshCallbackItem{mPostExecutionState=" + mPostExecutionState + "}";
+ }
+
+ private RefreshCallbackItem(Parcel in) {
+ mPostExecutionState = in.readInt();
+ }
+
+ public static final @NonNull Creator<RefreshCallbackItem> CREATOR =
+ new Creator<RefreshCallbackItem>() {
+
+ public RefreshCallbackItem createFromParcel(Parcel in) {
+ return new RefreshCallbackItem(in);
+ }
+
+ public RefreshCallbackItem[] newArray(int size) {
+ return new RefreshCallbackItem[size];
+ }
+ };
+}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index de1d38a..1ff0b79 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -126,10 +126,13 @@
final ClientTransactionItem item = callbacks.get(i);
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
final int postExecutionState = item.getPostExecutionState();
- final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
- item.getPostExecutionState());
- if (closestPreExecutionState != UNDEFINED) {
- cycleToPath(r, closestPreExecutionState, transaction);
+
+ if (item.shouldHaveDefinedPreExecutionState()) {
+ final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+ item.getPostExecutionState());
+ if (closestPreExecutionState != UNDEFINED) {
+ cycleToPath(r, closestPreExecutionState, transaction);
+ }
}
item.execute(mTransactionHandler, token, mPendingActions);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 708a02d..7d7232e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.GameManager;
+import android.app.GrammaticalInflectionManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
@@ -3973,6 +3974,8 @@
CREDENTIAL_SERVICE,
DEVICE_LOCK_SERVICE,
VIRTUALIZATION_SERVICE,
+ GRAMMATICAL_INFLECTION_SERVICE,
+
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -5784,7 +5787,6 @@
*
* @see #getSystemService(String)
* @see android.content.om.OverlayManager
- * @hide
*/
public static final String OVERLAY_SERVICE = "overlay";
@@ -6170,6 +6172,14 @@
public static final String VIRTUALIZATION_SERVICE = "virtualization";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link GrammaticalInflectionManager}.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -7281,7 +7291,9 @@
* Updates the device ID association of this Context. Since a Context created with
* {@link #createDeviceContext} cannot change its device association, this method must
* not be called for instances created with {@link #createDeviceContext}.
- *
+ *<p>
+ * Note that updating the deviceId of the Context will not update its associated display.
+ *</p>
* @param deviceId The new device ID to assign to this Context.
* @throws UnsupportedOperationException if the method is called on an instance that was
* created with {@link Context#createDeviceContext(int)}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8aa0454..265e02a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1814,8 +1814,8 @@
* Package manager install result code. @hide because result codes are not
* yet ready to be exposed.
*/
- public static final String EXTRA_INSTALL_RESULT
- = "android.intent.extra.INSTALL_RESULT";
+ @SystemApi
+ public static final String EXTRA_INSTALL_RESULT = "android.intent.extra.INSTALL_RESULT";
/**
* Activity Action: Launch application uninstaller.
@@ -1841,6 +1841,7 @@
* Specify whether the package should be uninstalled for all users.
* @hide because these should not be part of normal application flow.
*/
+ @SystemApi
public static final String EXTRA_UNINSTALL_ALL_USERS
= "android.intent.extra.UNINSTALL_ALL_USERS";
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index e4936bc..7e787c9 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -78,16 +78,14 @@
*
* @see OverlayManager
* @see OverlayManagerTransaction
- * @hide
*/
public class FabricatedOverlay {
/**
* Retrieves the identifier for this fabricated overlay.
* @return the overlay identifier
- *
- * @hide
*/
+ @NonNull
public OverlayIdentifier getIdentifier() {
return new OverlayIdentifier(
mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
@@ -325,7 +323,6 @@
* @param overlayName a name used to uniquely identify the fabricated overlay owned by the
* caller itself.
* @param targetPackage the name of the package to be overlaid
- * @hide
*/
public FabricatedOverlay(@NonNull String overlayName, @NonNull String targetPackage) {
this(generateFabricatedOverlayInternal(
@@ -344,7 +341,6 @@
* should specify which overlayable to be overlaid.
*
* @param targetOverlayable the overlayable name defined in target package.
- * @hide
*/
public void setTargetOverlayable(@Nullable String targetOverlayable) {
mOverlay.targetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
@@ -438,7 +434,6 @@
* @param value the integer representing the new value
* @param configuration The string representation of the config this overlay is enabled for
* @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type
- * @hide
*/
@NonNull
public void setResourceValue(
@@ -470,7 +465,6 @@
* @param value the string representing the new value
* @param configuration The string representation of the config this overlay is enabled for
* @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type
- * @hide
*/
@NonNull
public void setResourceValue(
@@ -491,7 +485,6 @@
* [package]:type/entry)
* @param value the file descriptor whose contents are the value of the frro
* @param configuration The string representation of the config this overlay is enabled for
- * @hide
*/
@NonNull
public void setResourceValue(
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
index a43091e..f256372 100644
--- a/core/java/android/content/om/OverlayIdentifier.java
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -41,7 +41,6 @@
* @see OverlayInfo#getOverlayIdentifier()
* @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
*/
-/** @hide */
@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
genEqualsHashCode = true, genToString = false)
public final class OverlayIdentifier implements Parcelable {
@@ -176,7 +175,6 @@
/**
* {@inheritDoc}
- * @hide
*/
@Override
@DataClass.Generated.Member
@@ -194,7 +192,6 @@
/**
* {@inheritDoc}
- * @hide
*/
@Override
@DataClass.Generated.Member
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index a81d16ab..ff1c088 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -46,9 +46,7 @@
* -->
*
* @see OverlayManager#getOverlayInfosForTarget(String)
- * @hide
*/
-@SystemApi
public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
/** @hide */
@@ -59,7 +57,6 @@
STATE_DISABLED,
STATE_ENABLED,
STATE_ENABLED_IMMUTABLE,
- // @Deprecated STATE_TARGET_IS_BEING_REPLACED,
STATE_OVERLAY_IS_BEING_REPLACED,
STATE_SYSTEM_UPDATE_UNINSTALL,
})
@@ -312,7 +309,6 @@
* Get the overlay name from the registered fabricated overlay.
*
* @return the overlay name
- * @hide
*/
@Override
@Nullable
@@ -324,10 +320,8 @@
* Returns the name of the target overlaid package.
*
* @return the target package name
- * @hide
*/
@Override
- @SystemApi
@NonNull
public String getTargetPackageName() {
return targetPackageName;
@@ -359,9 +353,7 @@
* Return the target overlayable name.
*
* @return the name of the target overlayable resources set
- * @hide
*/
- @SystemApi
@Override
@Nullable
public String getTargetOverlayableName() {
@@ -394,7 +386,6 @@
*
* @return an identifier representing the current overlay.
* @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
- * @hide
*/
@Override
@NonNull
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7803cb8..96b7603 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -54,9 +54,7 @@
* </ul>
*
* @see OverlayManagerTransaction
- * @hide
*/
-@SystemApi
@SystemService(Context.OVERLAY_SERVICE)
public class OverlayManager {
@@ -392,7 +390,6 @@
*
* @param targetPackageName the target package name
* @return a list of overlay information
- * @hide
*/
@NonNull
@NonUiContext
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index c7c605d..5fd695b 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -58,7 +58,6 @@
*
* @see OverlayManager
* @see FabricatedOverlay
- * @hide
*/
public final class OverlayManagerTransaction implements Parcelable {
// TODO: remove @hide from this class when OverlayManager is added to the
@@ -92,8 +91,6 @@
/**
* Get an overlay manager transaction with the specified handler.
* @param overlayManager handles this transaction.
- *
- * @hide
*/
public OverlayManagerTransaction(@NonNull OverlayManager overlayManager) {
this(new ArrayList<>(), Objects.requireNonNull(overlayManager));
@@ -291,8 +288,6 @@
/**
* {@inheritDoc}
- *
- * @hide
*/
@Override
public int describeContents() {
@@ -301,8 +296,6 @@
/**
* {@inheritDoc}
- *
- * @hide
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -340,7 +333,6 @@
*
* @throws IOException if there is a file operation error.
* @throws PackageManager.NameNotFoundException if the package name is not found.
- * @hide
*/
@NonUiContext
public void commit() throws PackageManager.NameNotFoundException, IOException {
@@ -374,8 +366,6 @@
* package or target overlayable is changed.
*
* @param overlay the overlay to register with the overlay manager
- *
- * @hide
*/
@NonNull
public void registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
@@ -389,7 +379,6 @@
*
* @see OverlayManager#getOverlayInfosForTarget(String)
* @see OverlayInfo#getOverlayIdentifier()
- * @hide
*/
@NonNull
public void unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index dab57fd..68a84e8 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -807,6 +807,7 @@
CONFIG_LAYOUT_DIRECTION,
CONFIG_COLOR_MODE,
CONFIG_FONT_SCALE,
+ CONFIG_GRAMMATICAL_GENDER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Config {}
@@ -917,6 +918,12 @@
public static final int CONFIG_COLOR_MODE = 0x4000;
/**
* Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the change to gender. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_GRAMMATICAL_GENDER = 0x8000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
* can itself handle asset path changes. Set from the {@link android.R.attr#configChanges}
* attribute. This is not a core resource configuration, but a higher-level value, so its
* constant starts at the high bits.
@@ -946,7 +953,6 @@
* not a core resource configuration, but a higher-level value, so its
* constant starts at the high bits.
*/
-
public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 0x10000000;
/** @hide
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 84811ea..94c5e25 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -18,9 +18,11 @@
import static android.os.Build.VERSION_CODES.DONUT;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -2253,6 +2255,8 @@
*
* @hide
*/
+ @SystemApi
+ @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
public boolean hasFragileUserData() {
return (privateFlags & PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) != 0;
}
@@ -2487,8 +2491,13 @@
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY) != 0;
}
- /** @hide */
+ /**
+ * @return {@code true} if the application is permitted to hold privileged permissions.
+ *
+ * @hide */
@TestApi
+ @SystemApi
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
public boolean isPrivilegedApp() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index febdaed..703a9252 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -26,6 +26,9 @@
import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_AUTO;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -48,6 +51,10 @@
import android.content.pm.PackageManager.DeleteFlags;
import android.content.pm.PackageManager.InstallReason;
import android.content.pm.PackageManager.InstallScenario;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.graphics.Bitmap;
import android.icu.util.ULocale;
import android.net.Uri;
@@ -70,12 +77,14 @@
import android.util.ArraySet;
import android.util.ExceptionUtils;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.util.DataClass;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.Closeable;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -172,10 +181,22 @@
public static final String ACTION_SESSION_UPDATED =
"android.content.pm.action.SESSION_UPDATED";
- /** {@hide} */
+ /**
+ * Intent action to indicate that user action is required for current install. This action can
+ * be used only by system apps.
+ *
+ * @hide
+ */
+ @SystemApi
public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
- /** @hide */
+ /**
+ * Activity Action: Intent sent to the installer when a session for requesting
+ * user pre-approval, and user needs to confirm the installation.
+ *
+ * @hide
+ */
+ @SystemApi
public static final String ACTION_CONFIRM_PRE_APPROVAL =
"android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -272,11 +293,23 @@
@Deprecated
public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
- /** {@hide} */
+ /**
+ * The status as used internally in the package manager. Refer to {@link PackageManager} for
+ * a list of all valid legacy statuses.
+ *
+ * @hide
+ */
+ @SystemApi
public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
/** {@hide} */
public static final String EXTRA_LEGACY_BUNDLE = "android.content.pm.extra.LEGACY_BUNDLE";
- /** {@hide} */
+ /**
+ * The callback to execute once an uninstall is completed (used for both successful and
+ * unsuccessful uninstalls).
+ *
+ * @hide
+ */
+ @SystemApi
public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
/**
@@ -293,6 +326,17 @@
public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
/**
+ * Path to the validated base APK for this session, which may point at an
+ * APK inside the session (when the session defines the base), or it may
+ * point at the existing base APK (when adding splits to an existing app).
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_RESOLVED_BASE_PATH =
+ "android.content.pm.extra.RESOLVED_BASE_PATH";
+
+ /**
* Streaming installation pending.
* Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
*
@@ -796,8 +840,6 @@
* @param statusReceiver Where to deliver the result of the operation indicated by the extra
* {@link #EXTRA_STATUS}. Refer to the individual status codes
* on how to handle them.
- *
- * @hide
*/
@RequiresPermission(anyOf = {
Manifest.permission.DELETE_PACKAGES,
@@ -1871,6 +1913,101 @@
}
/**
+ * Parse a single APK or a directory of APKs to get install relevant information about
+ * the package wrapped in {@link InstallInfo}.
+ * @throws PackageParsingException if the package source file(s) provided is(are) not valid,
+ * or the parser isn't able to parse the supplied source(s).
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public InstallInfo getInstallInfo(@NonNull File file, int flags)
+ throws PackageParsingException {
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+ input.reset(), file, flags);
+ if (result.isError()) {
+ throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage());
+ }
+ return new InstallInfo(result);
+ }
+
+ // (b/239722738) This class serves as a bridge between the PackageLite class, which
+ // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
+ // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
+ // public APIs.
+ /**
+ * Install related details from an APK or a folder of APK(s).
+ *
+ * @hide
+ */
+ @SystemApi
+ public static class InstallInfo {
+
+ /** @hide */
+ @IntDef(prefix = { "INSTALL_LOCATION_" }, value = {
+ INSTALL_LOCATION_AUTO,
+ INSTALL_LOCATION_INTERNAL_ONLY,
+ INSTALL_LOCATION_PREFER_EXTERNAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstallLocation{}
+
+ private PackageLite mPkg;
+
+ InstallInfo(ParseResult<PackageLite> result) {
+ mPkg = result.getResult();
+ }
+
+ /**
+ * See {@link PackageLite#getPackageName()}
+ */
+ @NonNull
+ public String getPackageName() {
+ return mPkg.getPackageName();
+ }
+
+ /**
+ * @return The default install location defined by an application in
+ * {@link android.R.attr#installLocation} attribute.
+ */
+ public @InstallLocation int getInstallLocation() {
+ return mPkg.getInstallLocation();
+ }
+
+ /**
+ * @param params {@link SessionParams} of the installation
+ * @return Total disk space occupied by an application after installation.
+ * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files,
+ * and all relevant native code.
+ * @throws IOException when size of native binaries cannot be calculated.
+ */
+ public long calculateInstalledSize(@NonNull SessionParams params) throws IOException {
+ return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride);
+ }
+ }
+
+ /**
+ * Generic exception class for using with parsing operations.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static class PackageParsingException extends Exception {
+ private final int mErrorCode;
+
+ /** {@hide} */
+ public PackageParsingException(int errorCode, @Nullable String detailedMessage) {
+ super(detailedMessage);
+ mErrorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+ }
+
+ /**
* Parameters for creating a new {@link PackageInstaller.Session}.
*/
public static class SessionParams implements Parcelable {
@@ -2431,9 +2568,7 @@
* By default this is the app that created the {@link PackageInstaller} object.
*
* @param installerPackageName name of the installer package
- * {@hide}
*/
- @TestApi
public void setInstallerPackageName(@Nullable String installerPackageName) {
this.installerPackageName = installerPackageName;
}
@@ -3430,8 +3565,6 @@
/**
* Returns the Uid of the owner of the session.
- *
- * @hide
*/
public int getInstallerUid() {
return installerUid;
@@ -3445,6 +3578,13 @@
return keepApplicationEnabledSetting;
}
+ /**
+ * Returns whether this session has requested user pre-approval.
+ */
+ public @NonNull boolean getIsPreApprovalRequested() {
+ return isPreapprovalRequested;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index cbdcc02..4ad657e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2250,6 +2250,7 @@
*
* @hide
*/
+ @SystemApi
public static final int DELETE_KEEP_DATA = 0x00000001;
/**
@@ -2258,6 +2259,7 @@
*
* @hide
*/
+ @SystemApi
public static final int DELETE_ALL_USERS = 0x00000002;
/**
@@ -2295,6 +2297,7 @@
*
* @hide
*/
+ @SystemApi
public static final int DELETE_SUCCEEDED = 1;
/**
@@ -2304,6 +2307,7 @@
*
* @hide
*/
+ @SystemApi
public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
/**
@@ -2313,6 +2317,7 @@
*
* @hide
*/
+ @SystemApi
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
/**
@@ -2332,9 +2337,11 @@
*
* @hide
*/
+ @SystemApi
public static final int DELETE_FAILED_OWNER_BLOCKED = -4;
/** {@hide} */
+ @SystemApi
public static final int DELETE_FAILED_ABORTED = -5;
/**
@@ -4090,6 +4097,17 @@
public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the requisite kernel support for migrating IPsec tunnels to new source/destination addresses.
+ *
+ * <p>This feature implies that the device supports XFRM Migration (CONFIG_XFRM_MIGRATE) and has
+ * the kernel fixes to support cross-address-family IPsec tunnel migration
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
+ "android.software.ipsec_tunnel_migration";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports a system interface for the user to select
* and bind device control services provided by applications.
@@ -5336,17 +5354,7 @@
throws NameNotFoundException;
/**
- * Return the UID associated with the given package name.
- * <p>
- * Note that the same package will have different UIDs under different
- * {@link UserHandle} on the same device.
- *
- * @param packageName The full name (i.e. com.google.apps.contacts) of the
- * desired package.
- * @param userId The user handle identifier to look up the package under.
- * @return Returns an integer UID who owns the given package name.
- * @throws NameNotFoundException if no such package is available to the
- * caller.
+ * See {@link #getPackageUidAsUser(String, PackageInfoFlags, int)}.
* @deprecated Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} instead.
* @hide
*/
@@ -5357,9 +5365,22 @@
int flags, @UserIdInt int userId) throws NameNotFoundException;
/**
- * See {@link #getPackageUidAsUser(String, int, int)}.
+ * Return the UID associated with the given package name.
+ * <p>
+ * Note that the same package will have different UIDs under different
+ * {@link UserHandle} on the same device.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user handle identifier to look up the package under.
+ * @return Returns an integer UID who owns the given package name.
+ * @throws NameNotFoundException if no such package is available to the
+ * caller.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
public int getPackageUidAsUser(@NonNull String packageName, @NonNull PackageInfoFlags flags,
@UserIdInt int userId) throws NameNotFoundException {
throw new UnsupportedOperationException(
@@ -9805,6 +9826,83 @@
}
/**
+ * A parcelable class to pass as an intent extra to the PackageInstaller. When an uninstall is
+ * completed (both successfully or unsuccessfully), the result is sent to the uninstall
+ * initiators.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class UninstallCompleteCallback implements Parcelable {
+ private IPackageDeleteObserver2 mBinder;
+
+ /** @hide */
+ @IntDef(prefix = { "DELETE_" }, value = {
+ DELETE_SUCCEEDED,
+ DELETE_FAILED_INTERNAL_ERROR,
+ DELETE_FAILED_DEVICE_POLICY_MANAGER,
+ DELETE_FAILED_USER_RESTRICTED,
+ DELETE_FAILED_OWNER_BLOCKED,
+ DELETE_FAILED_ABORTED,
+ DELETE_FAILED_USED_SHARED_LIBRARY,
+ DELETE_FAILED_APP_PINNED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeleteStatus{}
+
+ /** @hide */
+ public UninstallCompleteCallback(@NonNull IBinder binder) {
+ mBinder = IPackageDeleteObserver2.Stub.asInterface(binder);
+ }
+
+ /** @hide */
+ private UninstallCompleteCallback(Parcel in) {
+ mBinder = IPackageDeleteObserver2.Stub.asInterface(in.readStrongBinder());
+ }
+
+ public static final @NonNull Parcelable.Creator<UninstallCompleteCallback> CREATOR =
+ new Parcelable.Creator<>() {
+ public UninstallCompleteCallback createFromParcel(Parcel source) {
+ return new UninstallCompleteCallback(source);
+ }
+
+ public UninstallCompleteCallback[] newArray(int size) {
+ return new UninstallCompleteCallback[size];
+ }
+ };
+
+ /**
+ * Called when an uninstallation is completed successfully or unsuccessfully.
+ *
+ * @param packageName The name of the package being uninstalled.
+ * @param resultCode Result code of the operation.
+ * @param errorMessage Error message if any.
+ *
+ * @hide */
+ @SystemApi
+ public void onUninstallComplete(@NonNull String packageName, @DeleteStatus int resultCode,
+ @Nullable String errorMessage) {
+ try {
+ mBinder.onPackageDeleted(packageName, resultCode, errorMessage);
+ } catch (RemoteException e) {
+ // no-op
+ }
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mBinder.asBinder());
+ }
+ }
+
+ /**
* Return the install reason that was recorded when a package was first
* installed for a specific user. Requesting the install reason for another
* user will require the permission INTERACT_ACROSS_USERS_FULL.
@@ -10723,4 +10821,33 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Checks if a package is blocked from uninstall for a particular user. A package can be
+ * blocked from being uninstalled by a device owner or profile owner.
+ * See {@link DevicePolicyManager#setUninstallBlocked(ComponentName, String, boolean)}.
+ *
+ * @param packageName Name of the package being uninstalled.
+ * @param user UserHandle who's ability to uninstall a package is being checked.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public boolean canUserUninstall(@NonNull String packageName, @NonNull UserHandle user){
+ throw new UnsupportedOperationException(
+ "canUserUninstall not implemented in subclass");
+ }
+
+ /**
+ * See {@link android.provider.Settings.Global#SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public boolean shouldShowNewAppInstalledNotification() {
+ throw new UnsupportedOperationException(
+ "isShowNewAppInstalledNotificationEnabled not implemented in subclass");
+ }
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index f47c1e0..96aa624 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -46,6 +46,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.GrammaticalInflectionManager;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.LocaleProto;
@@ -141,6 +142,44 @@
@UnsupportedAppUsage
public boolean userSetLocale;
+ /**
+ * Current user preference for the grammatical gender.
+ */
+ @GrammaticalGender
+ private int mGrammaticalGender;
+
+ /** @hide */
+ @IntDef(prefix = { "GRAMMATICAL_GENDER_" }, value = {
+ GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ GRAMMATICAL_GENDER_NEUTRAL,
+ GRAMMATICAL_GENDER_FEMININE,
+ GRAMMATICAL_GENDER_MASCULINE,
+ })
+ public @interface GrammaticalGender {}
+
+ /**
+ * Constant for grammatical gender: to indicate the user has not specified the terms
+ * of address for the application.
+ */
+ public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0;
+
+ /**
+ * Constant for grammatical gender: to indicate the terms of address the user
+ * preferred in an application is neuter.
+ */
+ public static final int GRAMMATICAL_GENDER_NEUTRAL = 2;
+
+ /**
+ * Constant for grammatical gender: to indicate the terms of address the user
+ * preferred in an application is feminine.
+ */
+ public static final int GRAMMATICAL_GENDER_FEMININE = 3;
+
+ /**
+ * Constant for grammatical gender: to indicate the terms of address the user
+ * preferred in an application is masculine.
+ */
+ public static final int GRAMMATICAL_GENDER_MASCULINE = 4;
/** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */
public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;
@@ -1024,6 +1063,7 @@
}
o.fixUpLocaleList();
mLocaleList = o.mLocaleList;
+ mGrammaticalGender = o.mGrammaticalGender;
userSetLocale = o.userSetLocale;
touchscreen = o.touchscreen;
keyboard = o.keyboard;
@@ -1510,6 +1550,7 @@
seq = 0;
windowConfiguration.setToDefaults();
fontWeightAdjustment = FONT_WEIGHT_ADJUSTMENT_UNDEFINED;
+ mGrammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
/**
@@ -1712,6 +1753,10 @@
changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
fontWeightAdjustment = delta.fontWeightAdjustment;
}
+ if (delta.mGrammaticalGender != mGrammaticalGender) {
+ changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
+ mGrammaticalGender = delta.mGrammaticalGender;
+ }
return changed;
}
@@ -1929,6 +1974,10 @@
&& fontWeightAdjustment != delta.fontWeightAdjustment) {
changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
}
+
+ if (!publicOnly&& mGrammaticalGender != delta.mGrammaticalGender) {
+ changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
+ }
return changed;
}
@@ -2023,6 +2072,7 @@
dest.writeInt(assetsSeq);
dest.writeInt(seq);
dest.writeInt(fontWeightAdjustment);
+ dest.writeInt(mGrammaticalGender);
}
public void readFromParcel(Parcel source) {
@@ -2055,6 +2105,7 @@
assetsSeq = source.readInt();
seq = source.readInt();
fontWeightAdjustment = source.readInt();
+ mGrammaticalGender = source.readInt();
}
public static final @android.annotation.NonNull Parcelable.Creator<Configuration> CREATOR
@@ -2155,6 +2206,8 @@
if (n != 0) return n;
n = this.fontWeightAdjustment - that.fontWeightAdjustment;
if (n != 0) return n;
+ n = this.mGrammaticalGender - that.mGrammaticalGender;
+ if (n != 0) return n;
// if (n != 0) return n;
return n;
@@ -2196,10 +2249,37 @@
result = 31 * result + densityDpi;
result = 31 * result + assetsSeq;
result = 31 * result + fontWeightAdjustment;
+ result = 31 * result + mGrammaticalGender;
return result;
}
/**
+ * Returns the user preference for the grammatical gender. Will be
+ * {@link #GRAMMATICAL_GENDER_NOT_SPECIFIED} or
+ * {@link #GRAMMATICAL_GENDER_NEUTRAL} or
+ * {@link #GRAMMATICAL_GENDER_FEMININE} or
+ * {@link #GRAMMATICAL_GENDER_MASCULINE}.
+ *
+ * @return The preferred grammatical gender.
+ */
+ @GrammaticalGender
+ public int getGrammaticalGender() {
+ return mGrammaticalGender;
+ }
+
+ /**
+ * Sets the user preference for the grammatical gender. This is only for frameworks to easily
+ * override the gender in the configuration. To update the grammatical gender for an application
+ * use {@link GrammaticalInflectionManager#setRequestedApplicationGrammaticalGender(int)}.
+ *
+ * @param grammaticalGender The preferred grammatical gender.
+ * @hide
+ */
+ public void setGrammaticalGender(@GrammaticalGender int grammaticalGender) {
+ mGrammaticalGender = grammaticalGender;
+ }
+
+ /**
* Get the locale list. This is the preferred way for getting the locales (instead of using
* the direct accessor to {@link #locale}, which would only provide the primary locale).
*
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index a5a1fa689..b097bc0 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -83,7 +83,6 @@
* @return the resources provider instance for the {@code overlayInfo}
* @throws IOException when the files can't be loaded.
* @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
- * @hide
*/
@SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
@NonNull
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 0a14574..b8b1eaa 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -521,9 +521,7 @@
public static final int DATASPACE_BT2020_HLG = 168165376;
/**
- * ITU-R Recommendation 2020 (BT.2020)
- *
- * Ultra High-definition television.
+ * Perceptual Quantizer encoding.
*
* <p>Composed of the following -</p>
* <pre>
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9c42160..655e598 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -166,6 +166,14 @@
* The <code>android:keyboardLayout</code> attribute refers to a
* <a href="http://source.android.com/tech/input/key-character-map-files.html">
* key character map</a> resource that defines the keyboard layout.
+ * The <code>android:keyboardLocale</code> attribute specifies a comma separated list of BCP 47
+ * language tags depicting the locales supported by the keyboard layout. This attribute is
+ * optional and will be used for auto layout selection for external physical keyboards.
+ * The <code>android:keyboardLayoutType</code> attribute specifies the layoutType for the
+ * keyboard layout. This can be either empty or one of the following supported layout types:
+ * qwerty, qwertz, azerty, dvorak, colemak, workman, extended, turkish_q, turkish_f. This
+ * attribute is optional and will be used for auto layout selection for external physical
+ * keyboards.
* </p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -705,6 +713,30 @@
}
/**
+ * Returns the layout type of the queried layout
+ * <p>
+ * The input manager consults the built-in keyboard layouts as well as all keyboard layouts
+ * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
+ * </p>
+ *
+ * @param layoutDescriptor The layout descriptor of the queried layout
+ * @return layout type of the queried layout
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) {
+ KeyboardLayout[] layouts = getKeyboardLayouts();
+ for (KeyboardLayout kl : layouts) {
+ if (layoutDescriptor.equals(kl.getDescriptor())) {
+ return kl.getLayoutType();
+ }
+ }
+ return "";
+ }
+
+ /**
* Gets information about all supported keyboard layouts appropriate
* for a specific input device.
* <p>
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 52c1551..58f7759 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -21,24 +21,74 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Describes a keyboard layout.
*
* @hide
*/
-public final class KeyboardLayout implements Parcelable,
- Comparable<KeyboardLayout> {
+public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> {
private final String mDescriptor;
private final String mLabel;
private final String mCollection;
private final int mPriority;
@NonNull
private final LocaleList mLocales;
+ private final LayoutType mLayoutType;
private final int mVendorId;
private final int mProductId;
- public static final @android.annotation.NonNull Parcelable.Creator<KeyboardLayout> CREATOR =
- new Parcelable.Creator<KeyboardLayout>() {
+ /** Currently supported Layout types in the KCM files */
+ private enum LayoutType {
+ UNDEFINED(0, "undefined"),
+ QWERTY(1, "qwerty"),
+ QWERTZ(2, "qwertz"),
+ AZERTY(3, "azerty"),
+ DVORAK(4, "dvorak"),
+ COLEMAK(5, "colemak"),
+ WORKMAN(6, "workman"),
+ TURKISH_F(7, "turkish_f"),
+ TURKISH_Q(8, "turkish_q"),
+ EXTENDED(9, "extended");
+
+ private final int mValue;
+ private final String mName;
+ private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
+ static {
+ VALUE_TO_ENUM_MAP.put(UNDEFINED.mValue, UNDEFINED);
+ VALUE_TO_ENUM_MAP.put(QWERTY.mValue, QWERTY);
+ VALUE_TO_ENUM_MAP.put(QWERTZ.mValue, QWERTZ);
+ VALUE_TO_ENUM_MAP.put(AZERTY.mValue, AZERTY);
+ VALUE_TO_ENUM_MAP.put(DVORAK.mValue, DVORAK);
+ VALUE_TO_ENUM_MAP.put(COLEMAK.mValue, COLEMAK);
+ VALUE_TO_ENUM_MAP.put(WORKMAN.mValue, WORKMAN);
+ VALUE_TO_ENUM_MAP.put(TURKISH_F.mValue, TURKISH_F);
+ VALUE_TO_ENUM_MAP.put(TURKISH_Q.mValue, TURKISH_Q);
+ VALUE_TO_ENUM_MAP.put(EXTENDED.mValue, EXTENDED);
+ }
+
+ private static LayoutType of(int value) {
+ return VALUE_TO_ENUM_MAP.getOrDefault(value, UNDEFINED);
+ }
+
+ LayoutType(int value, String name) {
+ this.mValue = value;
+ this.mName = name;
+ }
+
+ private int getValue() {
+ return mValue;
+ }
+
+ private String getName() {
+ return mName;
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<KeyboardLayout> CREATOR = new Parcelable.Creator<>() {
public KeyboardLayout createFromParcel(Parcel source) {
return new KeyboardLayout(source);
}
@@ -48,12 +98,13 @@
};
public KeyboardLayout(String descriptor, String label, String collection, int priority,
- LocaleList locales, int vid, int pid) {
+ LocaleList locales, int layoutValue, int vid, int pid) {
mDescriptor = descriptor;
mLabel = label;
mCollection = collection;
mPriority = priority;
mLocales = locales;
+ mLayoutType = LayoutType.of(layoutValue);
mVendorId = vid;
mProductId = pid;
}
@@ -64,6 +115,7 @@
mCollection = source.readString();
mPriority = source.readInt();
mLocales = LocaleList.CREATOR.createFromParcel(source);
+ mLayoutType = LayoutType.of(source.readInt());
mVendorId = source.readInt();
mProductId = source.readInt();
}
@@ -106,6 +158,15 @@
}
/**
+ * Gets the layout type that this keyboard layout is intended for.
+ * This may be "undefined" if a layoutType has not been assigned to this keyboard layout.
+ * @return The keyboard layout's intended layout type.
+ */
+ public String getLayoutType() {
+ return mLayoutType.getName();
+ }
+
+ /**
* Gets the vendor ID of the hardware device this keyboard layout is intended for.
* Returns -1 if this is not specific to any piece of hardware.
* @return The hardware vendor ID of the keyboard layout's intended device.
@@ -135,6 +196,7 @@
dest.writeString(mCollection);
dest.writeInt(mPriority);
mLocales.writeToParcel(dest, 0);
+ dest.writeInt(mLayoutType.getValue());
dest.writeInt(mVendorId);
dest.writeInt(mProductId);
}
@@ -160,6 +222,7 @@
+ ", descriptor: " + mDescriptor
+ ", priority: " + mPriority
+ ", locales: " + mLocales.toString()
+ + ", layout type: " + mLayoutType.getName()
+ ", vendorId: " + mVendorId
+ ", productId: " + mProductId;
}
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 7faa285..727716e 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -65,12 +65,12 @@
*/
@Deprecated
public static final int PROGRAM_TYPE_INVALID = 0;
- /** Analogue AM radio (with or without RDS).
+ /** Analog AM radio (with or without RDS).
* @deprecated use {@link ProgramIdentifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_AM = 1;
- /** analogue FM radio (with or without RDS).
+ /** analog FM radio (with or without RDS).
* @deprecated use {@link ProgramIdentifier} instead
*/
@Deprecated
@@ -125,25 +125,50 @@
public @interface ProgramType {}
public static final int IDENTIFIER_TYPE_INVALID = 0;
- /** kHz */
+ /**
+ * Primary identifier for analog (without RDS) AM/FM stations:
+ * frequency in kHz.
+ *
+ * <p>This identifier also contains band information:
+ * <li>
+ * <ul><500kHz: AM LW.
+ * <ul>500kHz - 1705kHz: AM MW.
+ * <ul>1.71MHz - 30MHz: AM SW.
+ * <ul>>60MHz: FM.
+ * </li>
+ */
public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
- /** 16bit */
+ /**
+ * 16bit primary identifier for FM RDS station.
+ */
public static final int IDENTIFIER_TYPE_RDS_PI = 2;
/**
* 64bit compound primary identifier for HD Radio.
*
- * Consists of (from the LSB):
- * - 32bit: Station ID number;
- * - 4bit: HD_SUBCHANNEL;
- * - 18bit: AMFM_FREQUENCY.
- * The remaining bits should be set to zeros when writing on the chip side
+ * <p>Consists of (from the LSB):
+ * <li>
+ * <ul>132bit: Station ID number.
+ * <ul>14bit: HD_SUBCHANNEL.
+ * <ul>18bit: AMFM_FREQUENCY.
+ * </li>
+ *
+ * <p>While station ID number should be unique globally, it sometimes gets
+ * abused by broadcasters (i.e. not being set at all). To ensure local
+ * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is
+ * a best-effort - see {@link IDENTIFIER_TYPE_HD_STATION_NAME}.
+ *
+ * <p>HD Radio subchannel is a value in range of 0-7.
+ * This index is 0-based (where 0 is MPS and 1..7 are SPS),
+ * as opposed to HD Radio standard (where it's 1-based).
+ *
+ * <p>The remaining bits should be set to zeros when writing on the chip side
* and ignored when read.
*/
public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
/**
- * HD Radio subchannel - a value of range 0-7.
+ * HD Radio subchannel - a value in range of 0-7.
*
- * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
+ * <p>The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
* as opposed to HD Radio standard (where it's 1-based).
*
* @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead
@@ -153,16 +178,16 @@
/**
* 64bit additional identifier for HD Radio.
*
- * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
+ * <p>Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
* globally unique. To provide a best-effort solution, a short version of
* station name may be carried as additional identifier and may be used
* by the tuner hardware to double-check tuning.
*
- * The name is limited to the first 8 A-Z0-9 characters (lowercase letters
- * must be converted to uppercase). Encoded in little-endian ASCII:
- * the first character of the name is the LSB.
+ * <p>The name is limited to the first 8 A-Z0-9 characters (lowercase
+ * letters must be converted to uppercase). Encoded in little-endian
+ * ASCII: the first character of the name is the LSB.
*
- * For example: "Abc" is encoded as 0x434241.
+ * <p>For example: "Abc" is encoded as 0x434241.
*/
public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
/**
@@ -175,17 +200,19 @@
/**
* 28bit compound primary identifier for Digital Audio Broadcasting.
*
- * Consists of (from the LSB):
- * - 16bit: SId;
- * - 8bit: ECC code;
- * - 4bit: SCIdS.
+ * <p>Consists of (from the LSB):
+ * <li>
+ * <ul>16bit: SId.
+ * <ul>8bit: ECC code.
+ * <ul>4bit: SCIdS.
+ * </li>
*
- * SCIdS (Service Component Identifier within the Service) value
+ * <p>SCIdS (Service Component Identifier within the Service) value
* of 0 represents the main service, while 1 and above represents
* secondary services.
*
- * The remaining bits should be set to zeros when writing on the chip side
- * and ignored when read.
+ * <p>The remaining bits should be set to zeros when writing on the chip
+ * side and ignored when read.
*
* @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
*/
@@ -197,7 +224,9 @@
public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
/** kHz */
public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
- /** 24bit */
+ /**
+ * 24bit primary identifier for Digital Radio Mondiale.
+ */
public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
/** kHz */
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
@@ -207,7 +236,9 @@
*/
@Deprecated
public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
- /** 32bit */
+ /**
+ * 32bit primary identifier for SiriusXM Satellite Radio.
+ */
public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
/** 0-999 range */
public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
@@ -224,15 +255,15 @@
* of 0 represents the main service, while 1 and above represents
* secondary services.
*
- * The remaining bits should be set to zeros when writing on the chip side
- * and ignored when read.
+ * <p>The remaining bits should be set to zeros when writing on the chip
+ * side and ignored when read.
*/
public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
/**
* Primary identifier for vendor-specific radio technology.
* The value format is determined by a vendor.
*
- * It must not be used in any other programType than corresponding VENDOR
+ * <p>It must not be used in any other programType than corresponding VENDOR
* type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
* not be used in any program type other than 1015).
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index efe8238..b236d66 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -224,6 +224,7 @@
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
public static final String ACTION_USER_SETTINGS =
"android.settings.USER_SETTINGS";
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index a892570..bffa660 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -79,6 +79,12 @@
/** @hide */
public static final int PRIORITY_CATEGORY_CONVERSATIONS = 8;
+ /**
+ * Total number of priority categories. Keep updated with any updates to PriorityCategory enum.
+ * @hide
+ */
+ public static final int NUM_PRIORITY_CATEGORIES = 9;
+
/** @hide */
@IntDef(prefix = { "VISUAL_EFFECT_" }, value = {
VISUAL_EFFECT_FULL_SCREEN_INTENT,
@@ -107,6 +113,12 @@
/** @hide */
public static final int VISUAL_EFFECT_NOTIFICATION_LIST = 6;
+ /**
+ * Total number of visual effects. Keep updated with any updates to VisualEffect enum.
+ * @hide
+ */
+ public static final int NUM_VISUAL_EFFECTS = 7;
+
/** @hide */
@IntDef(prefix = { "PEOPLE_TYPE_" }, value = {
PEOPLE_TYPE_UNSET,
@@ -202,8 +214,8 @@
/** @hide */
public ZenPolicy() {
- mPriorityCategories = new ArrayList<>(Collections.nCopies(9, 0));
- mVisualEffects = new ArrayList<>(Collections.nCopies(7, 0));
+ mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
+ mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
}
/**
@@ -804,8 +816,12 @@
@Override
public ZenPolicy createFromParcel(Parcel source) {
ZenPolicy policy = new ZenPolicy();
- policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
- policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+ policy.mPriorityCategories = trimList(
+ source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
+ NUM_PRIORITY_CATEGORIES);
+ policy.mVisualEffects = trimList(
+ source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
+ NUM_VISUAL_EFFECTS);
policy.mPriorityCalls = source.readInt();
policy.mPriorityMessages = source.readInt();
policy.mConversationSenders = source.readInt();
@@ -832,6 +848,15 @@
.toString();
}
+ // Returns a list containing the first maxLength elements of the input list if the list is
+ // longer than that size. For the lists in ZenPolicy, this should not happen unless the input
+ // is corrupt.
+ private static ArrayList<Integer> trimList(ArrayList<Integer> list, int maxLength) {
+ if (list == null || list.size() <= maxLength) {
+ return list;
+ }
+ return new ArrayList<>(list.subList(0, maxLength));
+ }
private String priorityCategoriesToString() {
StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index a50e6db..f8df668 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -106,6 +106,10 @@
/**
* Event for changes to the network service state (cellular).
*
+ * <p>Requires {@link Manifest.permission#ACCESS_FINE_LOCATION} or {@link
+ * Manifest.permission#ACCESS_COARSE_LOCATION} depending on the accuracy of the location info
+ * listeners want to get.
+ *
* @hide
* @see ServiceStateListener#onServiceStateChanged
* @see ServiceState
@@ -485,8 +489,9 @@
* <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
* the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
- * of whether the calling app has carrier privileges.
+ * <p>Requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission in case that
+ * listener want to get location info in {@link CellIdentity} regardless of whether the calling
+ * app has carrier privileges.
*
* @hide
* @see RegistrationFailedListener#onRegistrationFailed
@@ -504,8 +509,9 @@
* <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
* the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
- * of whether the calling app has carrier privileges.
+ * <p>Requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission in case that
+ * listener want to get {@link BarringInfo} which includes location info in {@link CellIdentity}
+ * regardless of whether the calling app has carrier privileges.
*
* @hide
* @see BarringInfoListener#onBarringInfoChanged
@@ -691,10 +697,8 @@
* Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will
* receive all the information in {@link ServiceState}, otherwise the cellIdentity
* will be null if apps only holding the {@link Manifest.permission#ACCESS_COARSE_LOCATION}
- * permission.
- * Network operator name in long/short alphanumeric format and numeric id will be null if
- * apps holding neither {@link android.Manifest.permission#ACCESS_FINE_LOCATION} nor
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+ * permission. Network operator name in long/short alphanumeric format and numeric id will
+ * be null if apps holding neither {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
*
* @see ServiceState#STATE_EMERGENCY_ONLY
* @see ServiceState#STATE_IN_SERVICE
@@ -1284,6 +1288,9 @@
* {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
*
+ * If the calling app doesn't have {@link android.Manifest.permission#ACCESS_FINE_LOCATION},
+ * it will receive {@link CellIdentity} without location-sensitive information included.
+ *
* @param cellIdentity the CellIdentity, which must include the globally unique
* identifier
* for the cell (for example, all components of the CGI or ECGI).
@@ -1462,6 +1469,10 @@
* {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
*
+ * If the calling app doesn't have {@link android.Manifest.permission#ACCESS_FINE_LOCATION},
+ * it will receive {@link BarringInfo} including {@link CellIdentity} without
+ * location-sensitive information included.
+ *
* @param barringInfo for all services on the current cell.
* @see android.telephony.BarringInfo
*/
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 3b082bc..787ffb7 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
+import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.HardwareBuffer;
import android.window.SurfaceSyncGroup;
@@ -149,4 +150,21 @@
default SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
return null;
}
+
+ /**
+ * Set a crop region on all children parented to the layer represented by this
+ * AttachedSurfaceControl. This includes SurfaceView, and an example usage may
+ * be to ensure that SurfaceView with {@link android.view.SurfaceView#setZOrderOnTop}
+ * are cropped to a region not including the app bar.
+ *
+ * This cropped is expressed in terms of insets in window-space. Negative insets
+ * are considered invalid and will produce an exception. Insets of zero will produce
+ * the same result as if this function had never been called.
+ *
+ * @param insets The insets in each direction by which to bound the children
+ * expressed in window-space.
+ * @hide
+ */
+ default void setChildBoundingInsets(@NonNull Rect insets) {
+ }
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index b8cd7b9..f87b746 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
@@ -23,11 +24,15 @@
import android.annotation.Nullable;
import android.os.IBinder;
+import android.os.Process;
+import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
import java.util.function.Supplier;
@@ -48,8 +53,8 @@
/**
* Tracks whether {@link WindowInsetsController#show(int)} or
* {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation.
- * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)},
- * because the IME is being shown.
+ * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder,
+ * ImeTracker.Token)}, because the IME is being shown.
*/
private boolean mIsShowRequestedDuringHideAnimation;
@@ -76,7 +81,7 @@
// Remove IME surface as IME has finished hide animation, if there is no pending
// show request.
if (!mIsShowRequestedDuringHideAnimation) {
- notifyHidden();
+ notifyHidden(null /* statsToken */);
removeSurface();
}
}
@@ -120,7 +125,8 @@
* @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
*/
@Override
- public @ShowResult int requestShow(boolean fromIme) {
+ @ShowResult
+ public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
if (fromIme) {
ImeTracing.getInstance().triggerClientDump(
"ImeInsetsSourceConsumer#requestShow",
@@ -129,6 +135,9 @@
// TODO: ResultReceiver for IME.
// TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
+
if (getControl() == null) {
// If control is null, schedule to show IME when control is available.
mIsRequestedVisibleAwaitingControl = true;
@@ -140,16 +149,32 @@
return ShowResult.SHOW_IMMEDIATELY;
}
- return getImm().requestImeShow(mController.getHost().getWindowToken())
+ return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken)
? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
}
/**
* Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
* IME insets are hidden.
+ *
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
- private void notifyHidden() {
- getImm().notifyImeHidden(mController.getHost().getWindowToken());
+ private void notifyHidden(@Nullable ImeTracker.Token statsToken) {
+ // Create a new stats token to track the hide request when:
+ // - we do not already have one, or
+ // - we do already have one, but we have control and use the passed in token
+ // for the insets animation already.
+ if (statsToken == null || getControl() != null) {
+ statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
+ ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+ }
+
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
+
+ getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
+ Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
}
@Override
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 709bc2b..8abe66a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -41,6 +41,7 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Process;
import android.os.Trace;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -64,6 +65,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -977,7 +979,14 @@
@Override
public void show(@InsetsType int types) {
- show(types, false /* fromIme */, null /* statsToken */);
+ ImeTracker.Token statsToken = null;
+ if ((types & ime()) != 0) {
+ statsToken = ImeTracker.get().onRequestShow(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
+ }
+
+ show(types, false /* fromIme */, statsToken);
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -1054,7 +1063,14 @@
@Override
public void hide(@InsetsType int types) {
- hide(types, false /* fromIme */, null /* statsToken */);
+ ImeTracker.Token statsToken = null;
+ if ((types & ime()) != 0) {
+ statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+ }
+
+ hide(types, false /* fromIme */, statsToken);
}
@VisibleForTesting
@@ -1164,13 +1180,17 @@
if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
types &= ~mDisabledUserAnimationInsetsTypes;
- if (fromIme && (disabledTypes & ime()) != 0
- && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
- // We've requested IMM to show IME, but the IME is not controllable. We need to
- // cancel the request.
- setRequestedVisibleTypes(0 /* visibleTypes */, ime());
- if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
- notifyVisibilityChanged();
+ if ((disabledTypes & ime()) != 0) {
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+
+ if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
+ // We've requested IMM to show IME, but the IME is not controllable. We need to
+ // cancel the request.
+ setRequestedVisibleTypes(0 /* visibleTypes */, ime());
+ if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
+ notifyVisibilityChanged();
+ }
}
}
}
@@ -1182,6 +1202,8 @@
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
return;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+
cancelExistingControllers(types);
if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
mLastStartedAnimTypes |= types;
@@ -1189,7 +1211,7 @@
final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
- fromIme, types, controls, animationType);
+ fromIme, types, controls, animationType, statsToken);
int typesReady = typesReadyPair.first;
boolean imeReady = typesReadyPair.second;
if (DEBUG) Log.d(TAG, String.format(
@@ -1288,7 +1310,10 @@
* @return Pair of (types ready to animate, IME ready to animate).
*/
private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
- SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) {
+ SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
+ @Nullable ImeTracker.Token statsToken) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
+
int typesReady = 0;
boolean imeReady = true;
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -1301,7 +1326,7 @@
boolean canRun = true;
if (show) {
// Show request
- switch(consumer.requestShow(fromIme)) {
+ switch(consumer.requestShow(fromIme, statsToken)) {
case ShowResult.SHOW_IMMEDIATELY:
break;
case ShowResult.IME_SHOW_DELAYED:
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index f46eb34..47672a3 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -35,6 +35,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,10 +51,14 @@
public class InsetsSourceConsumer {
@Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED})
+ @IntDef(value = {
+ ShowResult.SHOW_IMMEDIATELY,
+ ShowResult.IME_SHOW_DELAYED,
+ ShowResult.IME_SHOW_FAILED
+ })
@interface ShowResult {
/**
- * Window type is ready to be shown, will be shown immidiately.
+ * Window type is ready to be shown, will be shown immediately.
*/
int SHOW_IMMEDIATELY = 0;
/**
@@ -71,11 +76,13 @@
protected final InsetsController mController;
protected final InsetsState mState;
private int mId;
- private final @InsetsType int mType;
+ @InsetsType
+ private final int mType;
private static final String TAG = "InsetsSourceConsumer";
private final Supplier<Transaction> mTransactionSupplier;
- private @Nullable InsetsSourceControl mSourceControl;
+ @Nullable
+ private InsetsSourceControl mSourceControl;
private boolean mHasWindowFocus;
/**
@@ -180,7 +187,7 @@
return true;
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = PACKAGE)
public InsetsSourceControl getControl() {
return mSourceControl;
}
@@ -280,10 +287,16 @@
* @param fromController {@code true} if request is coming from controller.
* (e.g. in IME case, controller is
* {@link android.inputmethodservice.InputMethodService}).
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+ *
+ * @implNote The {@code statsToken} is ignored here, and only handled in
+ * {@link ImeInsetsSourceConsumer} for IME animations only.
+ *
* @return @see {@link ShowResult}.
*/
- @VisibleForTesting
- public @ShowResult int requestShow(boolean fromController) {
+ @VisibleForTesting(visibility = PACKAGE)
+ @ShowResult
+ public int requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken) {
return ShowResult.SHOW_IMMEDIATELY;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ea7a64e..52232f8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -919,6 +919,8 @@
}
}
};
+ private final Rect mChildBoundingInsets = new Rect();
+ private boolean mChildBoundingInsetsChanged = false;
private String mTag = TAG;
@@ -2222,6 +2224,8 @@
mTempRect.inset(mWindowAttributes.surfaceInsets.left,
mWindowAttributes.surfaceInsets.top,
mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
+ mTempRect.inset(mChildBoundingInsets.left, mChildBoundingInsets.top,
+ mChildBoundingInsets.right, mChildBoundingInsets.bottom);
t.setWindowCrop(mBoundsLayer, mTempRect);
}
@@ -2243,7 +2247,7 @@
if (!sc.isValid()) return;
if (updateBoundsLayer(t)) {
- mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
+ applyTransactionOnDraw(t);
}
}
@@ -2329,7 +2333,6 @@
*/
void notifyRendererOfFramePending() {
if (mAttachInfo.mThreadedRenderer != null) {
- mAttachInfo.mThreadedRenderer.notifyCallbackPending();
mAttachInfo.mThreadedRenderer.notifyFramePending();
}
}
@@ -3447,7 +3450,8 @@
}
}
- if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
+ if (surfaceSizeChanged || surfaceReplaced || surfaceCreated ||
+ windowAttributesChanged || mChildBoundingInsetsChanged) {
// If the surface has been replaced, there's a chance the bounds layer is not parented
// to the new layer. When updating bounds layer, also reparent to the main VRI
// SurfaceControl to ensure it's correctly placed in the hierarchy.
@@ -3458,6 +3462,7 @@
// enough. WMS doesn't want to keep around old children since they will leak when the
// client creates new children.
prepareSurfaces();
+ mChildBoundingInsetsChanged = false;
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -11078,7 +11083,7 @@
@Nullable public SurfaceControl.Transaction buildReparentTransaction(
@NonNull SurfaceControl child) {
if (mSurfaceControl.isValid()) {
- return new SurfaceControl.Transaction().reparent(child, mSurfaceControl);
+ return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
}
return null;
}
@@ -11334,4 +11339,14 @@
}
mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
}
+
+ @Override
+ public void setChildBoundingInsets(@NonNull Rect insets) {
+ if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
+ throw new IllegalArgumentException("Negative insets passed to setChildBoundingInsets.");
+ }
+ mChildBoundingInsets.set(insets);
+ mChildBoundingInsetsChanged = true;
+ scheduleTraversals();
+ }
}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 6eae63a..f08f61f 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -40,6 +40,7 @@
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IImeTracker;
import com.android.internal.view.IInputMethodManager;
import java.util.ArrayList;
@@ -61,6 +62,9 @@
@Nullable
private static volatile IInputMethodManager sServiceCache = null;
+ @Nullable
+ private static volatile IImeTracker sTrackerServiceCache = null;
+
/**
* @return {@code true} if {@link IInputMethodManager} is available.
*/
@@ -527,4 +531,137 @@
throw e.rethrowFromSystemServer();
}
}
+
+ @AnyThread
+ @Nullable
+ static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
+ @SoftInputShowHideReason int reason) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return null;
+ }
+ try {
+ return service.onRequestShow(uid, origin, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ @Nullable
+ static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
+ @SoftInputShowHideReason int reason) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return null;
+ }
+ try {
+ return service.onRequestHide(uid, origin, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onProgress(statsToken, phase);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onFailed(statsToken, phase);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onCancelled(statsToken, phase);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ static void onShown(@NonNull IBinder statsToken) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onShown(statsToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ static void onHidden(@NonNull IBinder statsToken) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onHidden(statsToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+ static boolean hasPendingImeVisibilityRequests() {
+ final var service = getImeTrackerService();
+ if (service == null) {
+ return true;
+ }
+ try {
+ return service.hasPendingImeVisibilityRequests();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ @Nullable
+ private static IImeTracker getImeTrackerService() {
+ var trackerService = sTrackerServiceCache;
+ if (trackerService == null) {
+ final var service = getService();
+ if (service == null) {
+ return null;
+ }
+
+ try {
+ trackerService = service.getImeTrackerService();
+ if (trackerService == null) {
+ return null;
+ }
+
+ sTrackerServiceCache = trackerService;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return trackerService;
+ }
}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 927d769..1bcb040 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,9 +16,6 @@
package android.view.inputmethod;
-import static android.view.inputmethod.ImeTracker.Debug.originToString;
-import static android.view.inputmethod.ImeTracker.Debug.phaseToString;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,6 +43,46 @@
String TAG = "ImeTracker";
+ /** The type of the IME request. */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_SHOW,
+ TYPE_HIDE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Type {}
+
+ /** IME show request type. */
+ int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
+
+ /** IME hide request type. */
+ int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
+
+ /** The status of the IME request. */
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_RUN,
+ STATUS_CANCEL,
+ STATUS_FAIL,
+ STATUS_SUCCESS,
+ STATUS_TIMEOUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Status {}
+
+ /** IME request running. */
+ int STATUS_RUN = ImeProtoEnums.STATUS_RUN;
+
+ /** IME request cancelled. */
+ int STATUS_CANCEL = ImeProtoEnums.STATUS_CANCEL;
+
+ /** IME request failed. */
+ int STATUS_FAIL = ImeProtoEnums.STATUS_FAIL;
+
+ /** IME request succeeded. */
+ int STATUS_SUCCESS = ImeProtoEnums.STATUS_SUCCESS;
+
+ /** IME request timed out. */
+ int STATUS_TIMEOUT = ImeProtoEnums.STATUS_TIMEOUT;
+
/**
* The origin of the IME request
*
@@ -61,25 +98,17 @@
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
- /**
- * The IME show request originated in the client.
- */
- int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
+ /** The IME show request originated in the client. */
+ int ORIGIN_CLIENT_SHOW_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_SHOW_SOFT_INPUT;
- /**
- * The IME hide request originated in the client.
- */
- int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
+ /** The IME hide request originated in the client. */
+ int ORIGIN_CLIENT_HIDE_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_HIDE_SOFT_INPUT;
- /**
- * The IME show request originated in the server.
- */
- int ORIGIN_SERVER_START_INPUT = 2;
+ /** The IME show request originated in the server. */
+ int ORIGIN_SERVER_START_INPUT = ImeProtoEnums.ORIGIN_SERVER_START_INPUT;
- /**
- * The IME hide request originated in the server.
- */
- int ORIGIN_SERVER_HIDE_INPUT = 3;
+ /** The IME hide request originated in the server. */
+ int ORIGIN_SERVER_HIDE_INPUT = ImeProtoEnums.ORIGIN_SERVER_HIDE_INPUT;
/**
* The current phase of the IME request.
@@ -88,6 +117,7 @@
* where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
*/
@IntDef(prefix = { "PHASE_" }, value = {
+ PHASE_NOT_SET,
PHASE_CLIENT_VIEW_SERVED,
PHASE_SERVER_CLIENT_KNOWN,
PHASE_SERVER_CLIENT_FOCUSED,
@@ -121,6 +151,11 @@
PHASE_CLIENT_HANDLE_HIDE_INSETS,
PHASE_CLIENT_APPLY_ANIMATION,
PHASE_CLIENT_CONTROL_ANIMATION,
+ PHASE_CLIENT_DISABLED_USER_ANIMATION,
+ PHASE_CLIENT_COLLECT_SOURCE_CONTROLS,
+ PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
+ PHASE_CLIENT_REQUEST_IME_SHOW,
+ PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN,
PHASE_CLIENT_ANIMATION_RUNNING,
PHASE_CLIENT_ANIMATION_CANCEL,
PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
@@ -129,135 +164,172 @@
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
+ int PHASE_NOT_SET = ImeProtoEnums.PHASE_NOT_SET;
+
/** The view that requested the IME has been served by the IMM. */
- int PHASE_CLIENT_VIEW_SERVED = 0;
+ int PHASE_CLIENT_VIEW_SERVED = ImeProtoEnums.PHASE_CLIENT_VIEW_SERVED;
/** The IME client that requested the IME has window manager focus. */
- int PHASE_SERVER_CLIENT_KNOWN = 1;
+ int PHASE_SERVER_CLIENT_KNOWN = ImeProtoEnums.PHASE_SERVER_CLIENT_KNOWN;
/** The IME client that requested the IME has IME focus. */
- int PHASE_SERVER_CLIENT_FOCUSED = 2;
+ int PHASE_SERVER_CLIENT_FOCUSED = ImeProtoEnums.PHASE_SERVER_CLIENT_FOCUSED;
/** The IME request complies with the current accessibility settings. */
- int PHASE_SERVER_ACCESSIBILITY = 3;
+ int PHASE_SERVER_ACCESSIBILITY = ImeProtoEnums.PHASE_SERVER_ACCESSIBILITY;
/** The server is ready to run third party code. */
- int PHASE_SERVER_SYSTEM_READY = 4;
+ int PHASE_SERVER_SYSTEM_READY = ImeProtoEnums.PHASE_SERVER_SYSTEM_READY;
/** Checked the implicit hide request against any explicit show requests. */
- int PHASE_SERVER_HIDE_IMPLICIT = 5;
+ int PHASE_SERVER_HIDE_IMPLICIT = ImeProtoEnums.PHASE_SERVER_HIDE_IMPLICIT;
/** Checked the not-always hide request against any forced show requests. */
- int PHASE_SERVER_HIDE_NOT_ALWAYS = 6;
+ int PHASE_SERVER_HIDE_NOT_ALWAYS = ImeProtoEnums.PHASE_SERVER_HIDE_NOT_ALWAYS;
/** The server is waiting for a connection to the IME. */
- int PHASE_SERVER_WAIT_IME = 7;
+ int PHASE_SERVER_WAIT_IME = ImeProtoEnums.PHASE_SERVER_WAIT_IME;
/** The server has a connection to the IME. */
- int PHASE_SERVER_HAS_IME = 8;
+ int PHASE_SERVER_HAS_IME = ImeProtoEnums.PHASE_SERVER_HAS_IME;
/** The server decided the IME should be hidden. */
- int PHASE_SERVER_SHOULD_HIDE = 9;
+ int PHASE_SERVER_SHOULD_HIDE = ImeProtoEnums.PHASE_SERVER_SHOULD_HIDE;
/** Reached the IME wrapper. */
- int PHASE_IME_WRAPPER = 10;
+ int PHASE_IME_WRAPPER = ImeProtoEnums.PHASE_IME_WRAPPER;
/** Dispatched from the IME wrapper to the IME. */
- int PHASE_IME_WRAPPER_DISPATCH = 11;
+ int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH;
/** Reached the IME' showSoftInput method. */
- int PHASE_IME_SHOW_SOFT_INPUT = 12;
+ int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT;
/** Reached the IME' hideSoftInput method. */
- int PHASE_IME_HIDE_SOFT_INPUT = 13;
+ int PHASE_IME_HIDE_SOFT_INPUT = ImeProtoEnums.PHASE_IME_HIDE_SOFT_INPUT;
/** The server decided the IME should be shown. */
- int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14;
+ int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE;
/** Requested applying the IME visibility in the insets source consumer. */
- int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
+ int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER =
+ ImeProtoEnums.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER;
/** Applied the IME visibility. */
- int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
+ int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY;
/** Created the show IME runner. */
- int PHASE_WM_SHOW_IME_RUNNER = 17;
+ int PHASE_WM_SHOW_IME_RUNNER = ImeProtoEnums.PHASE_WM_SHOW_IME_RUNNER;
/** Ready to show IME. */
- int PHASE_WM_SHOW_IME_READY = 18;
+ int PHASE_WM_SHOW_IME_READY = ImeProtoEnums.PHASE_WM_SHOW_IME_READY;
/** The Window Manager has a connection to the IME insets control target. */
- int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19;
+ int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET =
+ ImeProtoEnums.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET;
/** Reached the window insets control target's show insets method. */
- int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20;
+ int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS =
+ ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS;
/** Reached the window insets control target's hide insets method. */
- int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21;
+ int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS =
+ ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS;
/** Reached the remote insets control target's show insets method. */
- int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22;
+ int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS =
+ ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS;
/** Reached the remote insets control target's hide insets method. */
- int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23;
+ int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS =
+ ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS;
/** Reached the remote insets controller. */
- int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24;
+ int PHASE_WM_REMOTE_INSETS_CONTROLLER = ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROLLER;
/** Created the IME window insets show animation. */
- int PHASE_WM_ANIMATION_CREATE = 25;
+ int PHASE_WM_ANIMATION_CREATE = ImeProtoEnums.PHASE_WM_ANIMATION_CREATE;
/** Started the IME window insets show animation. */
- int PHASE_WM_ANIMATION_RUNNING = 26;
+ int PHASE_WM_ANIMATION_RUNNING = ImeProtoEnums.PHASE_WM_ANIMATION_RUNNING;
/** Reached the client's show insets method. */
- int PHASE_CLIENT_SHOW_INSETS = 27;
+ int PHASE_CLIENT_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_SHOW_INSETS;
/** Reached the client's hide insets method. */
- int PHASE_CLIENT_HIDE_INSETS = 28;
+ int PHASE_CLIENT_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HIDE_INSETS;
/** Handling the IME window insets show request. */
- int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
+ int PHASE_CLIENT_HANDLE_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_SHOW_INSETS;
/** Handling the IME window insets hide request. */
- int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30;
+ int PHASE_CLIENT_HANDLE_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_HIDE_INSETS;
/** Applied the IME window insets show animation. */
- int PHASE_CLIENT_APPLY_ANIMATION = 31;
+ int PHASE_CLIENT_APPLY_ANIMATION = ImeProtoEnums.PHASE_CLIENT_APPLY_ANIMATION;
/** Started the IME window insets show animation. */
- int PHASE_CLIENT_CONTROL_ANIMATION = 32;
+ int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
+
+ /** Checked that the IME is controllable. */
+ int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_ANIMATION;
+
+ /** Collecting insets source controls. */
+ int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
+
+ /** Reached the insets source consumer's show request method. */
+ int PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW =
+ ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW;
+
+ /** Reached input method manager's request IME show method. */
+ int PHASE_CLIENT_REQUEST_IME_SHOW = ImeProtoEnums.PHASE_CLIENT_REQUEST_IME_SHOW;
+
+ /** Reached the insets source consumer's notify hidden method. */
+ int PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN =
+ ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN;
/** Queued the IME window insets show animation. */
- int PHASE_CLIENT_ANIMATION_RUNNING = 33;
+ int PHASE_CLIENT_ANIMATION_RUNNING = ImeProtoEnums.PHASE_CLIENT_ANIMATION_RUNNING;
/** Cancelled the IME window insets show animation. */
- int PHASE_CLIENT_ANIMATION_CANCEL = 34;
+ int PHASE_CLIENT_ANIMATION_CANCEL = ImeProtoEnums.PHASE_CLIENT_ANIMATION_CANCEL;
/** Finished the IME window insets show animation. */
- int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
+ int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_SHOW;
/** Finished the IME window insets hide animation. */
- int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36;
+ int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_HIDE;
/**
- * Called when an IME show request is created.
+ * Creates an IME show request tracking token.
*
- * @param token the token tracking the current IME show request or {@code null} otherwise.
+ * @param component the component name where the IME show request was created,
+ * or {@code null} otherwise
+ * (defaulting to {@link ActivityThread#currentProcessName()}).
+ * @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME show request.
* @param reason the reason why the IME show request was created.
+ *
+ * @return An IME tracking token.
*/
- void onRequestShow(@Nullable Token token, @Origin int origin,
+ @NonNull
+ Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason);
/**
- * Called when an IME hide request is created.
+ * Creates an IME hide request tracking token.
*
- * @param token the token tracking the current IME hide request or {@code null} otherwise.
+ * @param component the component name where the IME hide request was created,
+ * or {@code null} otherwise
+ * (defaulting to {@link ActivityThread#currentProcessName()}).
+ * @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME hide request.
* @param reason the reason why the IME hide request was created.
+ *
+ * @return An IME tracking token.
*/
- void onRequestHide(@Nullable Token token, @Origin int origin,
+ @NonNull
+ Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason);
/**
@@ -313,112 +385,122 @@
*/
@NonNull
static ImeTracker get() {
- return SystemProperties.getBoolean("persist.debug.imetracker", false)
- ? LOGGER
- : NOOP_LOGGER;
+ return LOGGER;
}
/** The singleton IME tracker instance. */
+ @NonNull
ImeTracker LOGGER = new ImeTracker() {
+ {
+ // Set logging flag initial value.
+ mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
+ // Update logging flag dynamically.
+ SystemProperties.addChangeCallback(() ->
+ mLogProgress =
+ SystemProperties.getBoolean("persist.debug.imetracker", false));
+ }
+
+ /** Whether progress should be logged. */
+ private boolean mLogProgress;
+
+ @NonNull
@Override
- public void onRequestShow(@Nullable Token token, int origin,
+ public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
- if (token == null) return;
- Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
+ IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
+ if (binder == null) binder = new Binder();
+
+ final Token token = Token.build(binder, component);
+
+ Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+
+ return token;
}
+ @NonNull
@Override
- public void onRequestHide(@Nullable Token token, int origin,
+ public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
- if (token == null) return;
- Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
+ IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
+ if (binder == null) binder = new Binder();
+
+ final Token token = Token.build(binder, component);
+
+ Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+
+ return token;
}
@Override
- public void onProgress(@Nullable Token token, int phase) {
+ public void onProgress(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
+ IInputMethodManagerGlobalInvoker.onProgress(token.mBinder, phase);
+
+ if (mLogProgress) {
+ Log.i(TAG, token.mTag + ": onProgress at " + Debug.phaseToString(phase));
+ }
}
@Override
- public void onFailed(@Nullable Token token, int phase) {
+ public void onFailed(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
+ IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+
+ Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
}
@Override
- public void onTodo(@Nullable Token token, int phase) {
+ public void onTodo(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
+ Log.i(TAG, token.mTag + ": onTodo at " + Debug.phaseToString(phase));
}
@Override
- public void onCancelled(@Nullable Token token, int phase) {
+ public void onCancelled(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
+ IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+
+ Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
}
@Override
public void onShown(@Nullable Token token) {
if (token == null) return;
+ IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+
Log.i(TAG, token.mTag + ": onShown");
}
@Override
public void onHidden(@Nullable Token token) {
if (token == null) return;
+ IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+
Log.i(TAG, token.mTag + ": onHidden");
}
};
- /** The singleton no-op IME tracker instance. */
- ImeTracker NOOP_LOGGER = new ImeTracker() {
-
- @Override
- public void onRequestShow(@Nullable Token token, int origin,
- @SoftInputShowHideReason int reason) {}
-
- @Override
- public void onRequestHide(@Nullable Token token, int origin,
- @SoftInputShowHideReason int reason) {}
-
- @Override
- public void onProgress(@Nullable Token token, int phase) {}
-
- @Override
- public void onFailed(@Nullable Token token, int phase) {}
-
- @Override
- public void onTodo(@Nullable Token token, int phase) {}
-
- @Override
- public void onCancelled(@Nullable Token token, int phase) {}
-
- @Override
- public void onShown(@Nullable Token token) {}
-
- @Override
- public void onHidden(@Nullable Token token) {}
- };
-
/** A token that tracks the progress of an IME request. */
class Token implements Parcelable {
- private final IBinder mBinder;
+ @NonNull
+ public final IBinder mBinder;
+
+ @NonNull
private final String mTag;
- public Token() {
- this(ActivityThread.currentProcessName());
+ @NonNull
+ private static Token build(@NonNull IBinder binder, @Nullable String component) {
+ if (component == null) component = ActivityThread.currentProcessName();
+ final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
+
+ return new Token(binder, tag);
}
- public Token(String component) {
- this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
- }
-
- private Token(IBinder binder, String tag) {
+ private Token(@NonNull IBinder binder, @NonNull String tag) {
mBinder = binder;
mTag = tag;
}
@@ -443,10 +525,11 @@
@NonNull
public static final Creator<Token> CREATOR = new Creator<>() {
+ @NonNull
@Override
public Token createFromParcel(Parcel source) {
- IBinder binder = source.readStrongBinder();
- String tag = source.readString8();
+ final IBinder binder = source.readStrongBinder();
+ final String tag = source.readString8();
return new Token(binder, tag);
}
@@ -458,22 +541,34 @@
}
/**
- * Utilities for mapping phases and origins IntDef values to their names.
+ * Utilities for mapping IntDef values to their names.
*
* Note: This is held in a separate class so that it only gets initialized when actually needed.
*/
class Debug {
+ private static final Map<Integer, String> sTypes =
+ getFieldMapping(ImeTracker.class, "TYPE_");
+ private static final Map<Integer, String> sStatus =
+ getFieldMapping(ImeTracker.class, "STATUS_");
private static final Map<Integer, String> sOrigins =
getFieldMapping(ImeTracker.class, "ORIGIN_");
private static final Map<Integer, String> sPhases =
getFieldMapping(ImeTracker.class, "PHASE_");
- public static String originToString(int origin) {
+ public static String typeToString(@Type int type) {
+ return sTypes.getOrDefault(type, "TYPE_" + type);
+ }
+
+ public static String statusToString(@Status int status) {
+ return sStatus.getOrDefault(status, "STATUS_" + status);
+ }
+
+ public static String originToString(@Origin int origin) {
return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
}
- public static String phaseToString(int phase) {
+ public static String phaseToString(@Phase int phase) {
return sPhases.getOrDefault(phase, "PHASE_" + phase);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8331155..e9f0d29 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2002,14 +2002,16 @@
* {@link #RESULT_HIDDEN}.
*/
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
- return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
+ return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
+ SoftInputShowHideReason.SHOW_SOFT_INPUT);
}
- private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
- final ImeTracker.Token statsToken = new ImeTracker.Token();
- ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
- reason);
+ private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ if (statsToken == null) {
+ statsToken = ImeTracker.get().onRequestShow(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
+ }
ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
null /* icProto */);
@@ -2057,8 +2059,8 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
synchronized (mH) {
- final ImeTracker.Token statsToken = new ImeTracker.Token();
- ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT);
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
@@ -2148,9 +2150,8 @@
private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final ImeTracker.Token statsToken = new ImeTracker.Token();
- ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
- reason);
+ final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
this, null /* icProto */);
@@ -2283,7 +2284,7 @@
hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
} else {
- showSoftInput(view, showFlags, null,
+ showSoftInput(view, null /* statsToken */, showFlags, null /* resultReceiver */,
SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT);
}
}
@@ -2796,8 +2797,8 @@
@UnsupportedAppUsage
void closeCurrentInput() {
- final ImeTracker.Token statsToken = new ImeTracker.Token();
- ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
synchronized (mH) {
@@ -2856,18 +2857,23 @@
*
* @param windowToken the window from which this request originates. If this doesn't match the
* currently served view, the request is ignored and returns {@code false}.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*
* @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
* @hide
*/
- public boolean requestImeShow(IBinder windowToken) {
+ public boolean requestImeShow(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
return false;
}
- showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */,
+
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+
+ showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
return true;
}
@@ -2878,12 +2884,15 @@
*
* @param windowToken the window from which this request originates. If this doesn't match the
* currently served view, the request is ignored.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
* @hide
*/
- public void notifyImeHidden(IBinder windowToken) {
- final ImeTracker.Token statsToken = new ImeTracker.Token();
- ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
- SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+ public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
+ if (statsToken == null) {
+ statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+ }
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
@@ -3549,6 +3558,18 @@
}
/**
+ * A test API for CTS to check whether there are any pending IME visibility requests.
+ *
+ * @return {@code true} iff there are pending IME visibility requests.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+ public boolean hasPendingImeVisibilityRequests() {
+ return IInputMethodManagerGlobalInvoker.hasPendingImeVisibilityRequests();
+ }
+
+ /**
* Show the settings for enabling subtypes of the specified input method.
*
* @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 500c41c..05717dd 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -31,7 +31,10 @@
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Spanned;
+import android.text.TextPaint;
import android.text.method.TransformationMethod;
+import android.text.style.CharacterStyle;
import android.widget.TextView;
import java.util.Objects;
@@ -182,6 +185,70 @@
mLinkTextColor = builder.mLinkTextColor;
}
+ /**
+ * Creates a new instance of {@link TextAppearanceInfo} by extracting text appearance from the
+ * character before cursor in the target {@link TextView}.
+ * @param textView the target {@link TextView}.
+ * @return the new instance of {@link TextAppearanceInfo}.
+ * @hide
+ */
+ @NonNull
+ public static TextAppearanceInfo createFromTextView(@NonNull TextView textView) {
+ final int selectionStart = textView.getSelectionStart();
+ final CharSequence text = textView.getText();
+ TextPaint textPaint = new TextPaint();
+ textPaint.set(textView.getPaint()); // Copy from textView
+ if (text instanceof Spanned && text.length() > 0 && selectionStart > 0) {
+ // Extract the CharacterStyle spans that changes text appearance in the character before
+ // cursor.
+ Spanned spannedText = (Spanned) text;
+ int lastCh = selectionStart - 1;
+ CharacterStyle[] spans = spannedText.getSpans(lastCh, lastCh, CharacterStyle.class);
+ if (spans != null) {
+ for (CharacterStyle span: spans) {
+ // Exclude spans that end at lastCh
+ if (spannedText.getSpanStart(span) <= lastCh
+ && lastCh < spannedText.getSpanEnd(span)) {
+ span.updateDrawState(textPaint); // Override the TextPaint
+ }
+ }
+ }
+ }
+ Typeface typeface = textPaint.getTypeface();
+ String systemFontFamilyName = null;
+ int textWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+ int textStyle = Typeface.NORMAL;
+ if (typeface != null) {
+ systemFontFamilyName = typeface.getSystemFontFamilyName();
+ textWeight = typeface.getWeight();
+ textStyle = typeface.getStyle();
+ }
+ TextAppearanceInfo.Builder builder = new TextAppearanceInfo.Builder();
+ builder.setTextSize(textPaint.getTextSize())
+ .setTextLocales(textPaint.getTextLocales())
+ .setSystemFontFamilyName(systemFontFamilyName)
+ .setTextFontWeight(textWeight)
+ .setTextStyle(textStyle)
+ .setShadowDx(textPaint.getShadowLayerDx())
+ .setShadowDy(textPaint.getShadowLayerDy())
+ .setShadowRadius(textPaint.getShadowLayerRadius())
+ .setShadowColor(textPaint.getShadowLayerColor())
+ .setElegantTextHeight(textPaint.isElegantTextHeight())
+ .setLetterSpacing(textPaint.getLetterSpacing())
+ .setFontFeatureSettings(textPaint.getFontFeatureSettings())
+ .setFontVariationSettings(textPaint.getFontVariationSettings())
+ .setTextScaleX(textPaint.getTextScaleX())
+ .setTextColor(textPaint.getColor())
+ .setLinkTextColor(textPaint.linkColor)
+ .setAllCaps(textView.isAllCaps())
+ .setFallbackLineSpacing(textView.isFallbackLineSpacing())
+ .setLineBreakStyle(textView.getLineBreakStyle())
+ .setLineBreakWordStyle(textView.getLineBreakWordStyle())
+ .setHighlightTextColor(textView.getHighlightColor())
+ .setHintTextColor(textView.getCurrentHintTextColor());
+ return builder.build();
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0a3ea8a..e643080 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,7 +21,6 @@
import android.R;
import android.animation.ValueAnimator;
-import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -38,7 +37,6 @@
import android.content.UndoOwner;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -51,10 +49,8 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RenderNode;
-import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.fonts.FontStyle;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
@@ -4774,41 +4770,7 @@
}
if (includeTextAppearance) {
- Typeface typeface = mTextView.getPaint().getTypeface();
- String systemFontFamilyName = null;
- int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
- if (typeface != null) {
- systemFontFamilyName = typeface.getSystemFontFamilyName();
- textFontWeight = typeface.getWeight();
- }
- ColorStateList linkTextColors = mTextView.getLinkTextColors();
- @ColorInt int linkTextColor = linkTextColors != null
- ? linkTextColors.getDefaultColor() : 0;
-
- TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder();
- appearanceBuilder.setTextSize(mTextView.getTextSize())
- .setTextLocales(mTextView.getTextLocales())
- .setSystemFontFamilyName(systemFontFamilyName)
- .setTextFontWeight(textFontWeight)
- .setTextStyle(mTextView.getTypefaceStyle())
- .setAllCaps(mTextView.isAllCaps())
- .setShadowDx(mTextView.getShadowDx())
- .setShadowDy(mTextView.getShadowDy())
- .setShadowRadius(mTextView.getShadowRadius())
- .setShadowColor(mTextView.getShadowColor())
- .setElegantTextHeight(mTextView.isElegantTextHeight())
- .setFallbackLineSpacing(mTextView.isFallbackLineSpacing())
- .setLetterSpacing(mTextView.getLetterSpacing())
- .setFontFeatureSettings(mTextView.getFontFeatureSettings())
- .setFontVariationSettings(mTextView.getFontVariationSettings())
- .setLineBreakStyle(mTextView.getLineBreakStyle())
- .setLineBreakWordStyle(mTextView.getLineBreakWordStyle())
- .setTextScaleX(mTextView.getTextScaleX())
- .setHighlightTextColor(mTextView.getHighlightColor())
- .setTextColor(mTextView.getCurrentTextColor())
- .setHintTextColor(mTextView.getCurrentHintTextColor())
- .setLinkTextColor(linkTextColor);
- builder.setTextAppearanceInfo(appearanceBuilder.build());
+ builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView));
}
imm.updateCursorAnchorInfo(mTextView, builder.build());
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index f74d294..be9cbff 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -123,7 +123,7 @@
private void receive(
int resultCode, Bundle resultData,
- @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+ @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
final int callbackId = resultData.getInt(RESULT_KEY_ID);
if (resultCode == RESULT_CODE_REGISTER) {
int priority = resultData.getInt(RESULT_KEY_PRIORITY);
@@ -140,11 +140,11 @@
@NonNull IOnBackInvokedCallback iCallback,
@OnBackInvokedDispatcher.Priority int priority,
int callbackId,
- @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+ @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
final ImeOnBackInvokedCallback imeCallback =
new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
mImeCallbacks.add(imeCallback);
- receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback);
+ receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority);
}
private void unregisterReceivedCallback(
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index f1635eb..ec9184b 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -23,6 +23,7 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeProtoEnums;
import android.view.inputmethod.InputMethodManager;
import java.lang.annotation.Retention;
@@ -64,113 +65,114 @@
SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED,
SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION,
- SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR})
+ SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR
+})
public @interface SoftInputShowHideReason {
/** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
- int SHOW_SOFT_INPUT = 0;
+ int SHOW_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT;
/** Show soft input when {@code InputMethodManagerService#attachNewInputLocked} called. */
- int ATTACH_NEW_INPUT = 1;
+ int ATTACH_NEW_INPUT = ImeProtoEnums.REASON_ATTACH_NEW_INPUT;
/** Show soft input by {@code InputMethodManagerService#showMySoftInput}. This is triggered when
* the IME process try to show the keyboard.
*
* @see android.inputmethodservice.InputMethodService#requestShowSelf(int)
*/
- int SHOW_SOFT_INPUT_FROM_IME = 2;
+ int SHOW_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_FROM_IME;
/**
* Hide soft input by
* {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow}.
*/
- int HIDE_SOFT_INPUT = 3;
+ int HIDE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_SOFT_INPUT;
/**
* Hide soft input by
* {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)}.
*/
- int HIDE_SOFT_INPUT_FROM_IME = 4;
+ int HIDE_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_FROM_IME;
/**
* Show soft input when navigated forward to the window (with
- * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}} which the focused view is text
+ * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}) which the focused view is text
* editor and system will auto-show the IME when the window can resize or running on a large
* screen.
*/
- int SHOW_AUTO_EDITOR_FORWARD_NAV = 5;
+ int SHOW_AUTO_EDITOR_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_AUTO_EDITOR_FORWARD_NAV;
/**
* Show soft input when navigated forward to the window with
* {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
* {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE}.
*/
- int SHOW_STATE_VISIBLE_FORWARD_NAV = 6;
+ int SHOW_STATE_VISIBLE_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_STATE_VISIBLE_FORWARD_NAV;
/**
* Show soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE}.
*/
- int SHOW_STATE_ALWAYS_VISIBLE = 7;
+ int SHOW_STATE_ALWAYS_VISIBLE = ImeProtoEnums.REASON_SHOW_STATE_ALWAYS_VISIBLE;
/**
* Show soft input during {@code InputMethodManagerService} receive changes from
* {@code SettingsProvider}.
*/
- int SHOW_SETTINGS_ON_CHANGE = 8;
+ int SHOW_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_SHOW_SETTINGS_ON_CHANGE;
/** Hide soft input during switching user. */
- int HIDE_SWITCH_USER = 9;
+ int HIDE_SWITCH_USER = ImeProtoEnums.REASON_HIDE_SWITCH_USER;
/** Hide soft input when the user is invalid. */
- int HIDE_INVALID_USER = 10;
+ int HIDE_INVALID_USER = ImeProtoEnums.REASON_HIDE_INVALID_USER;
/**
* Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_UNSPECIFIED} which
* the focused view is not text editor.
*/
- int HIDE_UNSPECIFIED_WINDOW = 11;
+ int HIDE_UNSPECIFIED_WINDOW = ImeProtoEnums.REASON_HIDE_UNSPECIFIED_WINDOW;
/**
* Hide soft input when navigated forward to the window with
* {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
* {@link LayoutParams#SOFT_INPUT_STATE_HIDDEN}.
*/
- int HIDE_STATE_HIDDEN_FORWARD_NAV = 12;
+ int HIDE_STATE_HIDDEN_FORWARD_NAV = ImeProtoEnums.REASON_HIDE_STATE_HIDDEN_FORWARD_NAV;
/**
* Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}.
*/
- int HIDE_ALWAYS_HIDDEN_STATE = 13;
+ int HIDE_ALWAYS_HIDDEN_STATE = ImeProtoEnums.REASON_HIDE_ALWAYS_HIDDEN_STATE;
/** Hide soft input when "adb shell ime <command>" called. */
- int HIDE_RESET_SHELL_COMMAND = 14;
+ int HIDE_RESET_SHELL_COMMAND = ImeProtoEnums.REASON_HIDE_RESET_SHELL_COMMAND;
/**
* Hide soft input during {@code InputMethodManagerService} receive changes from
* {@code SettingsProvider}.
*/
- int HIDE_SETTINGS_ON_CHANGE = 15;
+ int HIDE_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_HIDE_SETTINGS_ON_CHANGE;
/**
* Hide soft input from {@link com.android.server.policy.PhoneWindowManager} when setting
* {@link com.android.internal.R.integer#config_shortPressOnPowerBehavior} in config.xml as
* dismiss IME.
*/
- int HIDE_POWER_BUTTON_GO_HOME = 16;
+ int HIDE_POWER_BUTTON_GO_HOME = ImeProtoEnums.REASON_HIDE_POWER_BUTTON_GO_HOME;
/** Hide soft input when attaching docked stack. */
- int HIDE_DOCKED_STACK_ATTACHED = 17;
+ int HIDE_DOCKED_STACK_ATTACHED = ImeProtoEnums.REASON_HIDE_DOCKED_STACK_ATTACHED;
/**
* Hide soft input when {@link com.android.server.wm.RecentsAnimationController} starts
* intercept touch from app window.
*/
- int HIDE_RECENTS_ANIMATION = 18;
+ int HIDE_RECENTS_ANIMATION = ImeProtoEnums.REASON_HIDE_RECENTS_ANIMATION;
/**
* Hide soft input when {@link com.android.wm.shell.bubbles.BubbleController} is expanding,
* switching, or collapsing Bubbles.
*/
- int HIDE_BUBBLES = 19;
+ int HIDE_BUBBLES = ImeProtoEnums.REASON_HIDE_BUBBLES;
/**
* Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no
@@ -183,74 +185,78 @@
* only the dialog focused as it's the latest window with input focus) makes we need to hide
* soft-input when the same window focused again to align with the same behavior prior to R.
*/
- int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20;
+ int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR =
+ ImeProtoEnums.REASON_HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR;
/**
* Hide soft input when a {@link com.android.internal.inputmethod.IInputMethodClient} is
* removed.
*/
- int HIDE_REMOVE_CLIENT = 21;
+ int HIDE_REMOVE_CLIENT = ImeProtoEnums.REASON_HIDE_REMOVE_CLIENT;
/**
* Show soft input when the system invoking
* {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}.
*/
- int SHOW_RESTORE_IME_VISIBILITY = 22;
+ int SHOW_RESTORE_IME_VISIBILITY = ImeProtoEnums.REASON_SHOW_RESTORE_IME_VISIBILITY;
/**
* Show soft input by
* {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
*/
- int SHOW_TOGGLE_SOFT_INPUT = 23;
+ int SHOW_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_TOGGLE_SOFT_INPUT;
/**
* Hide soft input by
* {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
*/
- int HIDE_TOGGLE_SOFT_INPUT = 24;
+ int HIDE_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_TOGGLE_SOFT_INPUT;
/**
* Show soft input by
* {@link android.view.InsetsController#show(int)};
*/
- int SHOW_SOFT_INPUT_BY_INSETS_API = 25;
+ int SHOW_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_BY_INSETS_API;
/**
* Hide soft input if Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
* See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}.
*/
- int HIDE_DISPLAY_IME_POLICY_HIDE = 26;
+ int HIDE_DISPLAY_IME_POLICY_HIDE = ImeProtoEnums.REASON_HIDE_DISPLAY_IME_POLICY_HIDE;
/**
* Hide soft input by {@link android.view.InsetsController#hide(int)}.
*/
- int HIDE_SOFT_INPUT_BY_INSETS_API = 27;
+ int HIDE_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_INSETS_API;
/**
* Hide soft input by {@link android.inputmethodservice.InputMethodService#handleBack(boolean)}.
*/
- int HIDE_SOFT_INPUT_BY_BACK_KEY = 28;
+ int HIDE_SOFT_INPUT_BY_BACK_KEY = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_BACK_KEY;
/**
* Hide soft input by
* {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}.
*/
- int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT = 29;
+ int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT =
+ ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT;
/**
* Hide soft input by
* {@link android.inputmethodservice.InputMethodService#onExtractingInputChanged(EditorInfo)})}.
*/
- int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED = 30;
+ int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED =
+ ImeProtoEnums.REASON_HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED;
/**
* Hide soft input by the deprecated
* {@link InputMethodManager#hideSoftInputFromInputMethod(IBinder, int)}.
*/
- int HIDE_SOFT_INPUT_IMM_DEPRECATION = 31;
+ int HIDE_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IMM_DEPRECATION;
/**
* Hide soft input when the window gained focus without an editor from the IME shown window.
*/
- int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR = 32;
+ int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR =
+ ImeProtoEnums.REASON_HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR;
}
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 0df006d..65655b7 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -841,7 +841,19 @@
return sw.toString();
}
- final public String printCurrentState(long now) {
+ /**
+ * Returns current CPU state with all the processes as a String, sorted by load
+ * in descending order.
+ */
+ public final String printCurrentState(long now) {
+ return printCurrentState(now, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns current CPU state with the top {@code maxProcessesToDump} highest load
+ * processes as a String, sorted by load in descending order.
+ */
+ public final String printCurrentState(long now, int maxProcessesToDump) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
buildWorkingProcs();
@@ -883,8 +895,8 @@
if (DEBUG) Slog.i(TAG, "totalTime " + totalTime + " over sample time "
+ (mCurrentSampleTime-mLastSampleTime));
- int N = mWorkingProcs.size();
- for (int i=0; i<N; i++) {
+ int dumpedProcessCount = Math.min(maxProcessesToDump, mWorkingProcs.size());
+ for (int i = 0; i < dumpedProcessCount; i++) {
Stats st = mWorkingProcs.get(i);
printProcessCPU(pw, st.added ? " +" : (st.removed ? " -": " "),
st.pid, st.name, (int)st.rel_uptime,
diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/view/IImeTracker.aidl
new file mode 100644
index 0000000..b062ca7
--- /dev/null
+++ b/core/java/com/android/internal/view/IImeTracker.aidl
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.view.inputmethod.ImeTracker;
+
+/**
+ * Interface to the global Ime tracker, used by all client applications.
+ * {@hide}
+ */
+interface IImeTracker {
+
+ /**
+ * Called when an IME show request is created,
+ * returns a new Binder to be associated with the IME tracking token.
+ *
+ * @param uid the uid of the client that requested the IME.
+ * @param origin the origin of the IME show request.
+ * @param reason the reason why the IME show request was created.
+ */
+ IBinder onRequestShow(int uid, int origin, int reason);
+
+ /**
+ * Called when an IME hide request is created,
+ * returns a new Binder to be associated with the IME tracking token.
+ *
+ * @param uid the uid of the client that requested the IME.
+ * @param origin the origin of the IME hide request.
+ * @param reason the reason why the IME hide request was created.
+ */
+ IBinder onRequestHide(int uid, int origin, int reason);
+
+ /**
+ * Called when the IME request progresses to a further phase.
+ *
+ * @param statsToken the token tracking the current IME request.
+ * @param phase the new phase the IME request reached.
+ */
+ oneway void onProgress(in IBinder statsToken, int phase);
+
+ /**
+ * Called when the IME request fails.
+ *
+ * @param statsToken the token tracking the current IME request.
+ * @param phase the phase the IME request failed at.
+ */
+ oneway void onFailed(in IBinder statsToken, int phase);
+
+ /**
+ * Called when the IME request is cancelled.
+ *
+ * @param statsToken the token tracking the current IME request.
+ * @param phase the phase the IME request was cancelled at.
+ */
+ oneway void onCancelled(in IBinder statsToken, int phase);
+
+ /**
+ * Called when the IME show request is successful.
+ *
+ * @param statsToken the token tracking the current IME request.
+ */
+ oneway void onShown(in IBinder statsToken);
+
+ /**
+ * Called when the IME hide request is successful.
+ *
+ * @param statsToken the token tracking the current IME request.
+ */
+ oneway void onHidden(in IBinder statsToken);
+
+ /**
+ * Checks whether there are any pending IME visibility requests.
+ *
+ * @return {@code true} iff there are pending IME visibility requests.
+ */
+ @EnforcePermission("TEST_INPUT_METHOD")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.TEST_INPUT_METHOD)")
+ boolean hasPendingImeVisibilityRequests();
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 00bc3f2..2106426 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -27,6 +27,7 @@
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.view.IImeTracker;
/**
* Public interface to the global input method manager, used by all client
@@ -158,4 +159,10 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
void setStylusWindowIdleTimeoutForTest(in IInputMethodClient client, long timeout);
+
+ /**
+ * Returns the singleton instance for the Ime Tracker Service.
+ * {@hide}
+ */
+ IImeTracker getImeTrackerService();
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1072f57..1d1c02d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9401,8 +9401,34 @@
<attr name="label" />
<!-- The key character map file resource. -->
<attr name="keyboardLayout" format="reference" />
- <!-- The locales the given keyboard layout corresponds to. -->
- <attr name="locale" format="string" />
+ <!-- The locales the given keyboard layout corresponds to. This is a list of
+ BCP-47 conformant language tags separated by the delimiter ',' or '|'.
+ Some examples of language tags are: en-US, zh-Hans-CN, el-Grek-polyton.
+ It includes information for language code, country code, variant, and script
+ code like ‘Latn’, ‘Cyrl’, etc. -->
+ <attr name="keyboardLocale" format="string"/>
+ <!-- The layout type of the given keyboardLayout.
+ NOTE: The enum to int value mapping must remain stable -->
+ <attr name="keyboardLayoutType" format="enum">
+ <!-- Qwerty-based keyboard layout. -->
+ <enum name="qwerty" value="1" />
+ <!-- Qwertz-based keyboard layout. -->
+ <enum name="qwertz" value="2" />
+ <!-- Azerty-based keyboard layout. -->
+ <enum name="azerty" value="3" />
+ <!-- Dvorak keyboard layout. -->
+ <enum name="dvorak" value="4" />
+ <!-- Colemak keyboard layout. -->
+ <enum name="colemak" value="5" />
+ <!-- Workman keyboard layout. -->
+ <enum name="workman" value="6" />
+ <!-- Turkish-Q keyboard layout. -->
+ <enum name="turkish_q" value="7" />
+ <!-- Turkish-F keyboard layout. -->
+ <enum name="turkish_f" value="8" />
+ <!-- Keyboard layout that has been enhanced with a large number of extra characters. -->
+ <enum name="extended" value="9" />
+ </attr>
<!-- The vendor ID of the hardware the given layout corresponds to. @hide -->
<attr name="vendorId" format="integer" />
<!-- The product ID of the hardware the given layout corresponds to. @hide -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 44f85da..57eff9a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1027,6 +1027,9 @@
<flag name="layoutDirection" value="0x2000" />
<!-- The color mode of the screen has changed (color gamut or dynamic range). -->
<flag name="colorMode" value="0x4000" />
+ <!-- The grammatical gender has changed, for example the user set the grammatical gender
+ from the UI. -->
+ <flag name="grammaticalGender" value="0x8000" />
<!-- The font scaling factor has changed, that is the user has
selected a new global font size. -->
<flag name="fontScale" value="0x40000000" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 307707f..35ff7e8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5366,6 +5366,12 @@
TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
<bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
+ <!-- Whether camera compat treatment is enabled for issues caused by orientation mismatch
+ between camera buffers and an app window. This includes force rotation of fixed
+ orientation activities connected to the camera in fullscreen and showing a tooltip in
+ split screen. -->
+ <bool name="config_isWindowManagerCameraCompatTreatmentEnabled">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 2924ddf..97feaac 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -122,6 +122,8 @@
<public name="physicalKeyboardHintLanguageTag" />
<public name="physicalKeyboardHintLayoutType" />
<public name="allowSharedIsolatedProcess" />
+ <public name="keyboardLocale" />
+ <public name="keyboardLayoutType" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 923ef32..c73f2f4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4432,6 +4432,7 @@
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
+ <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
index 87f91fa..9a999e4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -72,9 +72,9 @@
/* value= */ 94300);
private static final ProgramSelector.Identifier RDS_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
- private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
- /* value= */ 0x10000111);
+ private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000111L);
private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
/* value= */ 0x1013);
@@ -89,7 +89,7 @@
private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+ Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
/* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
@@ -216,7 +216,7 @@
public void isPurge_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+ Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
}
@@ -225,7 +225,7 @@
public void isComplete_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+ Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
}
@@ -234,7 +234,7 @@
public void getModified_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+ Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Modified program info in chunk")
.that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
@@ -244,10 +244,10 @@
public void getRemoved_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
- Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+ Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
- .containsExactly(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
+ .containsExactly(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
}
@Test
@@ -276,6 +276,19 @@
}
@Test
+ public void getProgramList_forTunerAdapterWhenServiceDied_fails() throws Exception {
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ createRadioTuner();
+ doThrow(new RemoteException()).when(mTunerMock).startProgramListUpdates(any());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.getProgramList(parameters));
+
+ assertWithMessage("Exception for getting program list when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void getDynamicProgramList_forTunerAdapter() throws Exception {
createRadioTuner();
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
index 5bd018b..9399907 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
@@ -36,18 +36,18 @@
private static final long AM_FREQUENCY = 700;
private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(
ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
- private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_1 =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
- /* value= */ 0x1000011);
- private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_2 =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
- /* value= */ 0x10000112);
+ private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER_1 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000111L);
+ private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER_2 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000112L);
private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
/* value= */ 0x1001);
private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
- /* value= */ 94500);
+ /* value= */ 220352);
@Test
public void getType_forIdentifier() {
@@ -80,13 +80,13 @@
@Test
public void equals_withDifferentTypesForIdentifiers_returnsFalse() {
assertWithMessage("Identifier with different identifier type")
- .that(FM_IDENTIFIER).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+ .that(FM_IDENTIFIER).isNotEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1);
}
@Test
public void equals_withDifferentValuesForIdentifiers_returnsFalse() {
assertWithMessage("Identifier with different identifier value")
- .that(DAB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+ .that(DAB_DMB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1);
}
@Test
@@ -168,19 +168,19 @@
@Test
public void getFirstId_withIdInSelector() {
ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
- DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
- long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+ long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT);
assertWithMessage("Value of the first DAB_SID_EXT identifier")
- .that(firstIdValue).isEqualTo(DAB_SID_EXT_IDENTIFIER_1.getValue());
+ .that(firstIdValue).isEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1.getValue());
}
@Test
public void getFirstId_withIdNotInSelector() {
ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
- DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2};
+ DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2};
ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
int idType = ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY;
@@ -195,13 +195,13 @@
@Test
public void getAllIds_withIdInSelector() {
ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
- DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
ProgramSelector.Identifier[] allIdsExpected =
- {DAB_SID_EXT_IDENTIFIER_1, DAB_SID_EXT_IDENTIFIER_2};
+ {DAB_DMB_SID_EXT_IDENTIFIER_1, DAB_DMB_SID_EXT_IDENTIFIER_2};
ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
ProgramSelector.Identifier[] allIds =
- selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+ selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT);
assertWithMessage("All DAB_SID_EXT identifiers in selector")
.that(allIds).isEqualTo(allIdsExpected);
@@ -244,14 +244,14 @@
@Test
public void withSecondaryPreferred() {
ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
- DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+ DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
long[] vendorIdsExpected = {12345, 678};
ProgramSelector selector = getDabSelector(secondaryIds, vendorIdsExpected);
ProgramSelector.Identifier[] secondaryIdsExpected = new ProgramSelector.Identifier[]{
- DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_1};
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_1};
ProgramSelector selectorPreferred =
- selector.withSecondaryPreferred(DAB_SID_EXT_IDENTIFIER_1);
+ selector.withSecondaryPreferred(DAB_DMB_SID_EXT_IDENTIFIER_1);
assertWithMessage("Program type")
.that(selectorPreferred.getProgramType()).isEqualTo(selector.getProgramType());
@@ -458,7 +458,7 @@
private ProgramSelector getDabSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
@Nullable long[] vendorIds) {
- return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_SID_EXT_IDENTIFIER_1, secondaryIds,
+ return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_DMB_SID_EXT_IDENTIFIER_1, secondaryIds,
vendorIds);
}
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index 03742eb..afbf8c3 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -18,7 +18,9 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -105,17 +107,17 @@
private static final int INFO_FLAGS = 0b110001;
private static final int SIGNAL_QUALITY = 2;
private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
- /* value= */ 0x10000111);
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000111L);
private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_RELATED =
- new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
- /* value= */ 0x10000113);
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000113L);
private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
/* value= */ 0x1013);
private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
- /* value= */ 95500);
+ /* value= */ 220352);
private static final ProgramSelector DAB_SELECTOR =
new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, DAB_SID_EXT_IDENTIFIER,
new ProgramSelector.Identifier[]{
@@ -859,13 +861,13 @@
@Test
public void getLogicallyTunedTo_forProgramInfo() {
assertWithMessage("Identifier logically tuned to in DAB program info")
- .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER);
+ .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER);
}
@Test
public void getPhysicallyTunedTo_forProgramInfo() {
assertWithMessage("Identifier physically tuned to DAB program info")
- .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER);
+ .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER);
}
@Test
@@ -1006,6 +1008,35 @@
}
@Test
+ public void listModules_forRadioManagerWithNullListAsInput_fails() throws Exception {
+ createRadioManager();
+
+ assertWithMessage("Status when listing module with empty list input")
+ .that(mRadioManager.listModules(null)).isEqualTo(RadioManager.STATUS_BAD_VALUE);
+ }
+
+ @Test
+ public void listModules_withNullListFromService_fails() throws Exception {
+ createRadioManager();
+ when(mRadioServiceMock.listModules()).thenReturn(null);
+ List<RadioManager.ModuleProperties> modules = new ArrayList<>();
+
+ assertWithMessage("Status for listing module when getting null list from HAL client")
+ .that(mRadioManager.listModules(modules)).isEqualTo(RadioManager.STATUS_ERROR);
+ }
+
+ @Test
+ public void listModules_whenServiceDied_fails() throws Exception {
+ createRadioManager();
+ when(mRadioServiceMock.listModules()).thenThrow(new RemoteException());
+ List<RadioManager.ModuleProperties> modules = new ArrayList<>();
+
+ assertWithMessage("Status for listing module when HAL client service is dead")
+ .that(mRadioManager.listModules(modules))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void openTuner_forRadioModule() throws Exception {
createRadioManager();
int moduleId = 0;
@@ -1019,6 +1050,18 @@
}
@Test
+ public void openTuner_whenServiceDied_returnsNull() throws Exception {
+ createRadioManager();
+ when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()))
+ .thenThrow(new RemoteException());
+
+ RadioTuner nullTuner = mRadioManager.openTuner(/* moduleId= */ 0, FM_BAND_CONFIG,
+ /* withAudio= */ true, mCallbackMock, /* handler= */ null);
+
+ assertWithMessage("Radio tuner when service is dead").that(nullTuner).isNull();
+ }
+
+ @Test
public void addAnnouncementListener_withListenerNotAddedBefore() throws Exception {
createRadioManager();
Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
@@ -1049,6 +1092,21 @@
}
@Test
+ public void addAnnouncementListener_whenServiceDied_throwException() throws Exception {
+ createRadioManager();
+ String exceptionMessage = "service is dead";
+ when(mRadioServiceMock.addAnnouncementListener(any(), any()))
+ .thenThrow(new RemoteException(exceptionMessage));
+ Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener));
+
+ assertWithMessage("Exception for adding announcement listener with dead service")
+ .that(thrown).hasMessageThat().contains(exceptionMessage);
+ }
+
+ @Test
public void removeAnnouncementListener_withListenerNotAddedBefore_ignores() throws Exception {
createRadioManager();
@@ -1104,8 +1162,8 @@
}
private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) {
- return new RadioManager.ProgramInfo(selector, DAB_FREQUENCY_IDENTIFIER,
- DAB_SID_EXT_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
+ return new RadioManager.ProgramInfo(selector, DAB_SID_EXT_IDENTIFIER,
+ DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index d851a7724..c8b4493 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -18,10 +18,13 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,6 +40,7 @@
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
import android.os.Build;
+import android.os.RemoteException;
import org.junit.After;
import org.junit.Before;
@@ -46,7 +50,6 @@
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
-import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -131,6 +134,24 @@
}
@Test
+ public void setConfiguration_withInvalidParameters_fails() throws Exception {
+ doThrow(new IllegalArgumentException()).when(mTunerMock).setConfiguration(any());
+
+ assertWithMessage("Status for setting configuration with invalid parameters")
+ .that(mRadioTuner.setConfiguration(TEST_BAND_CONFIG))
+ .isEqualTo(RadioManager.STATUS_BAD_VALUE);
+ }
+
+ @Test
+ public void setConfiguration_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).setConfiguration(any());
+
+ assertWithMessage("Status for setting configuration when service is dead")
+ .that(mRadioTuner.setConfiguration(TEST_BAND_CONFIG))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void getConfiguration_forTunerAdapter() throws Exception {
when(mTunerMock.getConfiguration()).thenReturn(TEST_BAND_CONFIG);
RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
@@ -144,14 +165,52 @@
}
@Test
+ public void getConfiguration_withInvalidParameters_fails() throws Exception {
+ RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[0];
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> mRadioTuner.getConfiguration(bandConfigs));
+
+ assertWithMessage("Exception for getting configuration with invalid parameters")
+ .that(thrown).hasMessageThat().contains("must be an array of length 1");
+ }
+
+ @Test
+ public void getConfiguration_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).getConfiguration();
+ RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
+
+ assertWithMessage("Status for getting configuration when service is dead")
+ .that(mRadioTuner.getConfiguration(bandConfigs))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void setMute_forTunerAdapter() {
- int status = mRadioTuner.setMute(/* mute= */ true);
+ int status = mRadioTuner.setMute(true);
assertWithMessage("Status for setting mute")
.that(status).isEqualTo(RadioManager.STATUS_OK);
}
@Test
+ public void setMute_whenIllegalState_fails() throws Exception {
+ doThrow(new IllegalStateException()).when(mTunerMock).setMuted(anyBoolean());
+
+ assertWithMessage("Status for setting muted when service is in illegal state")
+ .that(mRadioTuner.setMute(true)).isEqualTo(RadioManager.STATUS_ERROR);
+ }
+
+ @Test
+ public void setMute_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).setMuted(anyBoolean());
+
+ assertWithMessage("Status for setting muted when service is dead")
+ .that(mRadioTuner.setMute(true))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void getMute_forTunerAdapter() throws Exception {
when(mTunerMock.isMuted()).thenReturn(true);
@@ -161,6 +220,14 @@
}
@Test
+ public void getMute_whenServiceDied_returnsTrue() throws Exception {
+ when(mTunerMock.isMuted()).thenThrow(new RemoteException());
+
+ assertWithMessage("Status for getting muted when service is dead")
+ .that(mRadioTuner.getMute()).isEqualTo(true);
+ }
+
+ @Test
public void step_forTunerAdapter_succeeds() throws Exception {
doAnswer(invocation -> {
mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
@@ -176,6 +243,24 @@
}
@Test
+ public void step_whenIllegalState_fails() throws Exception {
+ doThrow(new IllegalStateException()).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+ assertWithMessage("Status for stepping when service is in illegal state")
+ .that(mRadioTuner.step(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+ .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void step_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+ assertWithMessage("Status for stepping when service is dead")
+ .that(mRadioTuner.step(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void scan_forTunerAdapter_succeeds() throws Exception {
doAnswer(invocation -> {
mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
@@ -191,13 +276,31 @@
}
@Test
+ public void scan_whenIllegalState_fails() throws Exception {
+ doThrow(new IllegalStateException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+ assertWithMessage("Status for scanning when service is in illegal state")
+ .that(mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+ .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void scan_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+ assertWithMessage("Status for scan when service is dead")
+ .that(mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void seek_forTunerAdapter_succeeds() throws Exception {
doAnswer(invocation -> {
mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
return RadioManager.STATUS_OK;
}).when(mTunerMock).seek(anyBoolean(), anyBoolean());
- int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+ int scanStatus = mRadioTuner.seek(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
assertWithMessage("Status for seeking")
@@ -212,13 +315,31 @@
return RadioManager.STATUS_OK;
}).when(mTunerMock).seek(anyBoolean(), anyBoolean());
- mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
+ mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true);
verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
}
@Test
+ public void seek_whenIllegalState_fails() throws Exception {
+ doThrow(new IllegalStateException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+ assertWithMessage("Status for seeking when service is in illegal state")
+ .that(mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+ .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void seek_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+ assertWithMessage("Status for seeking when service is dead")
+ .that(mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void tune_withChannelsForTunerAdapter_succeeds() {
int status = mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0);
@@ -228,6 +349,33 @@
}
@Test
+ public void tune_withInvalidChannel_fails() throws Exception {
+ doThrow(new IllegalArgumentException()).when(mTunerMock).tune(any());
+
+ assertWithMessage("Status for tuning when service is in illegal state")
+ .that(mRadioTuner.tune(/* channel= */ 300, /* subChannel= */ 0))
+ .isEqualTo(RadioManager.STATUS_BAD_VALUE);
+ }
+
+ @Test
+ public void tune_withChannelsWhenIllegalState_fails() throws Exception {
+ doThrow(new IllegalStateException()).when(mTunerMock).tune(any());
+
+ assertWithMessage("Status for tuning when service is in illegal state")
+ .that(mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0))
+ .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void tune_withChannelsWhenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).tune(any());
+
+ assertWithMessage("Status for tuning when service is dead")
+ .that(mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0))
+ .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void tune_withValidSelectorForTunerAdapter_succeeds() throws Exception {
mRadioTuner.tune(FM_SELECTOR);
@@ -250,6 +398,17 @@
}
@Test
+ public void tune_withSelectorWhenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).tune(any());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.tune(FM_SELECTOR));
+
+ assertWithMessage("Exception for tuning when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void cancel_forTunerAdapter() throws Exception {
mRadioTuner.tune(FM_SELECTOR);
@@ -259,6 +418,22 @@
}
@Test
+ public void cancel_whenIllegalState_fails() throws Exception {
+ doThrow(new IllegalStateException()).when(mTunerMock).cancel();
+
+ assertWithMessage("Status for canceling when service is in illegal state")
+ .that(mRadioTuner.cancel()).isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void cancel_forTunerAdapterWhenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).cancel();
+
+ assertWithMessage("Status for canceling when service is dead")
+ .that(mRadioTuner.cancel()).isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+ }
+
+ @Test
public void cancelAnnouncement_forTunerAdapter() throws Exception {
mRadioTuner.cancelAnnouncement();
@@ -266,6 +441,17 @@
}
@Test
+ public void cancelAnnouncement_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).cancelAnnouncement();
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.cancelAnnouncement());
+
+ assertWithMessage("Exception for canceling announcement when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void getProgramInfo_beforeProgramInfoSetForTunerAdapter() {
RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
@@ -295,13 +481,24 @@
when(mTunerMock.getImage(anyInt())).thenReturn(bitmapExpected);
int imageId = 1;
- Bitmap image = mRadioTuner.getMetadataImage(/* id= */ imageId);
+ Bitmap image = mRadioTuner.getMetadataImage(imageId);
assertWithMessage("Image obtained from id %s", imageId)
.that(image).isEqualTo(bitmapExpected);
}
@Test
+ public void getMetadataImage_whenServiceDied_fails() throws Exception {
+ when(mTunerMock.getImage(anyInt())).thenThrow(new RemoteException());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.getMetadataImage(/* id= */ 1));
+
+ assertWithMessage("Exception for getting metadata image when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void startBackgroundScan_forTunerAdapter() throws Exception {
when(mTunerMock.startBackgroundScan()).thenReturn(false);
@@ -312,6 +509,17 @@
}
@Test
+ public void startBackgroundScan_whenServiceDied_fails() throws Exception {
+ when(mTunerMock.startBackgroundScan()).thenThrow(new RemoteException());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.startBackgroundScan());
+
+ assertWithMessage("Exception for background scan when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void isAnalogForced_forTunerAdapter() throws Exception {
when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
@@ -322,6 +530,19 @@
}
@Test
+ public void isAnalogForced_whenNotSupported_fails() throws Exception {
+ String errorMessage = "Analog forced switch is not supported";
+ when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG))
+ .thenThrow(new UnsupportedOperationException(errorMessage));
+
+ IllegalStateException thrown = assertThrows(IllegalStateException.class,
+ () -> mRadioTuner.isAnalogForced());
+
+ assertWithMessage("Exception for checking analog playback switch when not supported")
+ .that(thrown).hasMessageThat().contains(errorMessage);
+ }
+
+ @Test
public void setAnalogForced_forTunerAdapter() throws Exception {
boolean analogForced = true;
@@ -331,6 +552,19 @@
}
@Test
+ public void setAnalogForced_whenNotSupported_fails() throws Exception {
+ String errorMessage = "Analog forced switch is not supported";
+ doThrow(new UnsupportedOperationException(errorMessage))
+ .when(mTunerMock).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG), anyBoolean());
+
+ IllegalStateException thrown = assertThrows(IllegalStateException.class,
+ () -> mRadioTuner.setAnalogForced(/* isForced= */ false));
+
+ assertWithMessage("Exception for setting analog playback switch when not supported")
+ .that(thrown).hasMessageThat().contains(errorMessage);
+ }
+
+ @Test
public void isConfigFlagSupported_forTunerAdapter() throws Exception {
when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING))
.thenReturn(true);
@@ -343,6 +577,17 @@
}
@Test
+ public void isConfigFlagSupported_whenServiceDied_fails() throws Exception {
+ when(mTunerMock.isConfigFlagSupported(anyInt())).thenThrow(new RemoteException());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING));
+
+ assertWithMessage("Exception for checking config flag support when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void isConfigFlagSet_forTunerAdapter() throws Exception {
when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING))
.thenReturn(true);
@@ -355,6 +600,17 @@
}
@Test
+ public void isConfigFlagSet_whenServiceDied_fails() throws Exception {
+ when(mTunerMock.isConfigFlagSet(anyInt())).thenThrow(new RemoteException());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_DAB_DAB_LINKING));
+
+ assertWithMessage("Exception for getting config flag when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void setConfigFlag_forTunerAdapter() throws Exception {
boolean dabFmLinking = true;
@@ -364,8 +620,20 @@
}
@Test
+ public void setConfigFlag_whenServiceDied_fails() throws Exception {
+ doThrow(new RemoteException()).when(mTunerMock).setConfigFlag(anyInt(), anyBoolean());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.setConfigFlag(RadioManager.CONFIG_DAB_DAB_LINKING,
+ /* value= */ true));
+
+ assertWithMessage("Exception for setting config flag when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void getParameters_forTunerAdapter() throws Exception {
- List<String> parameterKeys = Arrays.asList("ParameterKeyMock");
+ List<String> parameterKeys = List.of("ParameterKeyMock");
Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
when(mTunerMock.getParameters(parameterKeys)).thenReturn(parameters);
@@ -374,6 +642,18 @@
}
@Test
+ public void getParameters_whenServiceDied_fails() throws Exception {
+ List<String> parameterKeys = List.of("ParameterKeyMock");
+ when(mTunerMock.getParameters(parameterKeys)).thenThrow(new RemoteException());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.getParameters(parameterKeys));
+
+ assertWithMessage("Exception for getting parameters when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void setParameters_forTunerAdapter() throws Exception {
Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
when(mTunerMock.setParameters(parameters)).thenReturn(parameters);
@@ -383,6 +663,18 @@
}
@Test
+ public void setParameters_whenServiceDied_fails() throws Exception {
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ when(mTunerMock.setParameters(parameters)).thenThrow(new RemoteException());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () -> mRadioTuner.setParameters(parameters));
+
+ assertWithMessage("Exception for setting parameters when service is dead")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
public void isAntennaConnected_forTunerAdapter() throws Exception {
mTunerCallback.onAntennaState(/* connected= */ false);
@@ -391,6 +683,15 @@
}
@Test
+ public void onError_forTunerAdapter() throws Exception {
+ int errorStatus = RadioTuner.ERROR_HARDWARE_FAILURE;
+
+ mTunerCallback.onError(errorStatus);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(errorStatus);
+ }
+
+ @Test
public void hasControl_forTunerAdapter() throws Exception {
when(mTunerMock.isClosed()).thenReturn(true);
@@ -398,6 +699,14 @@
}
@Test
+ public void hasControl_whenServiceDied_returnsFalse() throws Exception {
+ when(mTunerMock.isClosed()).thenThrow(new RemoteException());
+
+ assertWithMessage("Control on tuner when service is dead")
+ .that(mRadioTuner.hasControl()).isFalse();
+ }
+
+ @Test
public void onConfigurationChanged_forTunerCallbackAdapter() throws Exception {
mTunerCallback.onConfigurationChanged(TEST_BAND_CONFIG);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index a421218..82db716 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -19,6 +19,7 @@
import android.hardware.broadcastradio.Metadata;
import android.hardware.broadcastradio.ProgramIdentifier;
import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
@@ -41,17 +42,25 @@
/* dabFrequencyTable= */ null, /* vendorInfo= */ null);
}
- static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+ static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector,
+ ProgramSelector.Identifier logicallyTunedTo,
+ ProgramSelector.Identifier physicallyTunedTo, int signalQuality) {
return new RadioManager.ProgramInfo(selector,
- selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+ logicallyTunedTo, physicallyTunedTo, /* relatedContents= */ null,
/* infoFlags= */ 0, signalQuality,
new RadioMetadata.Builder().build(), new ArrayMap<>());
}
- static RadioManager.ProgramInfo makeProgramInfo(int programType,
- ProgramSelector.Identifier identifier, int signalQuality) {
- ProgramSelector selector = makeProgramSelector(programType, identifier);
- return makeProgramInfo(selector, signalQuality);
+ static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+ return makeProgramInfo(selector, selector.getPrimaryId(), selector.getPrimaryId(),
+ signalQuality);
+ }
+
+ static ProgramIdentifier makeHalIdentifier(@IdentifierType int type, long value) {
+ ProgramIdentifier halDabId = new ProgramIdentifier();
+ halDabId.type = type;
+ halDabId.value = value;
+ return halDabId;
}
static ProgramSelector makeFmSelector(long freq) {
@@ -67,44 +76,48 @@
}
static android.hardware.broadcastradio.ProgramSelector makeHalFmSelector(int freq) {
- ProgramIdentifier halId = new ProgramIdentifier();
- halId.type = IdentifierType.AMFM_FREQUENCY_KHZ;
- halId.value = freq;
-
- android.hardware.broadcastradio.ProgramSelector halSelector =
- new android.hardware.broadcastradio.ProgramSelector();
- halSelector.primaryId = halId;
- halSelector.secondaryIds = new ProgramIdentifier[0];
- return halSelector;
+ ProgramIdentifier halId = makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ, freq);
+ return makeHalSelector(halId, /* secondaryIds= */ new ProgramIdentifier[0]);
}
- static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
- // Note that because ConversionUtils does not by design provide functions for all
- // conversions, this function only copies fields that are set by makeProgramInfo().
- ProgramInfo hwInfo = new ProgramInfo();
- hwInfo.selector = ConversionUtils.programSelectorToHalProgramSelector(info.getSelector());
- hwInfo.logicallyTunedTo =
- ConversionUtils.identifierToHalProgramIdentifier(info.getLogicallyTunedTo());
- hwInfo.physicallyTunedTo =
- ConversionUtils.identifierToHalProgramIdentifier(info.getPhysicallyTunedTo());
- hwInfo.signalQuality = info.getSignalStrength();
- hwInfo.relatedContent = new ProgramIdentifier[]{};
- hwInfo.metadata = new Metadata[]{};
- return hwInfo;
+ static android.hardware.broadcastradio.ProgramSelector makeHalSelector(
+ ProgramIdentifier primaryId, ProgramIdentifier[] secondaryIds) {
+ android.hardware.broadcastradio.ProgramSelector hwSelector =
+ new android.hardware.broadcastradio.ProgramSelector();
+ hwSelector.primaryId = primaryId;
+ hwSelector.secondaryIds = secondaryIds;
+ return hwSelector;
}
static ProgramInfo makeHalProgramInfo(
android.hardware.broadcastradio.ProgramSelector hwSel, int hwSignalQuality) {
+ return makeHalProgramInfo(hwSel, hwSel.primaryId, hwSel.primaryId, hwSignalQuality);
+ }
+
+ static ProgramInfo makeHalProgramInfo(
+ android.hardware.broadcastradio.ProgramSelector hwSel,
+ ProgramIdentifier logicallyTunedTo, ProgramIdentifier physicallyTunedTo,
+ int hwSignalQuality) {
ProgramInfo hwInfo = new ProgramInfo();
hwInfo.selector = hwSel;
- hwInfo.logicallyTunedTo = hwSel.primaryId;
- hwInfo.physicallyTunedTo = hwSel.primaryId;
+ hwInfo.logicallyTunedTo = logicallyTunedTo;
+ hwInfo.physicallyTunedTo = physicallyTunedTo;
hwInfo.signalQuality = hwSignalQuality;
hwInfo.relatedContent = new ProgramIdentifier[]{};
hwInfo.metadata = new Metadata[]{};
return hwInfo;
}
+ static ProgramListChunk makeProgramListChunk(boolean purge, boolean complete,
+ ProgramInfo[] modified, ProgramIdentifier[] removed) {
+ ProgramListChunk halChunk = new ProgramListChunk();
+ halChunk.purge = purge;
+ halChunk.complete = complete;
+ halChunk.modified = modified;
+ halChunk.removed = removed;
+ return halChunk;
+ }
+
static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
VendorKeyValue vendorKeyValue = new VendorKeyValue();
vendorKeyValue.key = vendorKey;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index f404082..98103f6 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -173,6 +173,19 @@
}
@Test
+ public void openSession_withoutAudio_fails() throws Exception {
+ createBroadcastRadioService();
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+ /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock,
+ TARGET_SDK_VERSION));
+
+ assertWithMessage("Exception for opening session without audio")
+ .that(thrown).hasMessageThat().contains("not supported");
+ }
+
+ @Test
public void binderDied_forDeathRecipient() throws Exception {
createBroadcastRadioService();
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index a1cebb6..710c150 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -20,9 +20,13 @@
import android.hardware.broadcastradio.AmFmRegionConfig;
import android.hardware.broadcastradio.DabTableEntry;
import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
import android.hardware.broadcastradio.Properties;
import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.Build;
@@ -33,19 +37,20 @@
import org.junit.Test;
import java.util.Map;
+import java.util.Set;
public final class ConversionUtilsTest {
- private static final int FM_LOWER_LIMIT = 87500;
- private static final int FM_UPPER_LIMIT = 108000;
+ private static final int FM_LOWER_LIMIT = 87_500;
+ private static final int FM_UPPER_LIMIT = 108_000;
private static final int FM_SPACING = 200;
private static final int AM_LOWER_LIMIT = 540;
- private static final int AM_UPPER_LIMIT = 1700;
+ private static final int AM_UPPER_LIMIT = 1_700;
private static final int AM_SPACING = 10;
private static final String DAB_ENTRY_LABEL_1 = "5A";
- private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
+ private static final int DAB_ENTRY_FREQUENCY_1 = 174_928;
private static final String DAB_ENTRY_LABEL_2 = "12D";
- private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
+ private static final int DAB_ENTRY_FREQUENCY_2 = 229_072;
private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
@@ -57,6 +62,50 @@
private static final String TEST_VERSION = "versionMock";
private static final String TEST_SERIAL = "serialMock";
+ private static final int TEST_SIGNAL_QUALITY = 1;
+ private static final long TEST_DAB_DMB_SID_EXT_VALUE = 0xA000000111L;
+ private static final long TEST_DAB_ENSEMBLE_VALUE = 0x1001;
+ private static final long TEST_DAB_FREQUENCY_VALUE = 220_352;
+ private static final long TEST_FM_FREQUENCY_VALUE = 92_100;
+ private static final long TEST_VENDOR_ID_VALUE = 9_901;
+
+ private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, TEST_DAB_DMB_SID_EXT_VALUE);
+ private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID =
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, TEST_DAB_ENSEMBLE_VALUE);
+ private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID =
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, TEST_DAB_FREQUENCY_VALUE);
+ private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID =
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_FM_FREQUENCY_VALUE);
+ private static final ProgramSelector.Identifier TEST_VENDOR_ID =
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, TEST_VENDOR_ID_VALUE);
+
+ private static final ProgramIdentifier TEST_HAL_DAB_SID_EXT_ID =
+ AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_SID_EXT, TEST_DAB_DMB_SID_EXT_VALUE);
+ private static final ProgramIdentifier TEST_HAL_DAB_ENSEMBLE_ID =
+ AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_ENSEMBLE, TEST_DAB_ENSEMBLE_VALUE);
+ private static final ProgramIdentifier TEST_HAL_DAB_FREQUENCY_ID =
+ AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_FREQUENCY_KHZ,
+ TEST_DAB_FREQUENCY_VALUE);
+ private static final ProgramIdentifier TEST_HAL_FM_FREQUENCY_ID =
+ AidlTestUtils.makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ,
+ TEST_FM_FREQUENCY_VALUE);
+ private static final ProgramIdentifier TEST_HAL_VENDOR_ID =
+ AidlTestUtils.makeHalIdentifier(IdentifierType.VENDOR_START,
+ TEST_VENDOR_ID_VALUE);
+
+ private static final ProgramSelector TEST_DAB_SELECTOR = new ProgramSelector(
+ ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_EXT_ID,
+ new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID},
+ /* vendorIds= */ null);
+ private static final ProgramSelector TEST_FM_SELECTOR =
+ AidlTestUtils.makeFmSelector(TEST_FM_FREQUENCY_VALUE);
+
private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
@@ -159,6 +208,246 @@
}
@Test
+ public void identifierToHalProgramIdentifier_withDabId() {
+ ProgramIdentifier halDabId =
+ ConversionUtils.identifierToHalProgramIdentifier(TEST_DAB_SID_EXT_ID);
+
+ expect.withMessage("Converted HAL DAB identifier").that(halDabId)
+ .isEqualTo(TEST_HAL_DAB_SID_EXT_ID);
+ }
+
+ @Test
+ public void identifierFromHalProgramIdentifier_withDabId() {
+ ProgramSelector.Identifier dabId =
+ ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_DAB_SID_EXT_ID);
+
+ expect.withMessage("Converted DAB identifier").that(dabId).isEqualTo(TEST_DAB_SID_EXT_ID);
+ }
+
+ @Test
+ public void programSelectorToHalProgramSelector_withValidSelector() {
+ android.hardware.broadcastradio.ProgramSelector halDabSelector =
+ ConversionUtils.programSelectorToHalProgramSelector(TEST_DAB_SELECTOR);
+
+ expect.withMessage("Primary identifier of converted HAL DAB selector")
+ .that(halDabSelector.primaryId).isEqualTo(TEST_HAL_DAB_SID_EXT_ID);
+ expect.withMessage("Secondary identifiers of converted HAL DAB selector")
+ .that(halDabSelector.secondaryIds).asList()
+ .containsExactly(TEST_HAL_DAB_FREQUENCY_ID, TEST_HAL_DAB_ENSEMBLE_ID);
+ }
+
+ @Test
+ public void programSelectorToHalProgramSelector_withInvalidDabSelector_returnsNull() {
+ ProgramSelector invalidDbSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+ TEST_DAB_SID_EXT_ID,
+ new ProgramSelector.Identifier[0],
+ new long[0]);
+
+ android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+ ConversionUtils.programSelectorToHalProgramSelector(invalidDbSelector);
+
+ expect.withMessage("Invalid HAL DAB selector without required secondary ids")
+ .that(invalidHalDabSelector).isNull();
+ }
+
+ @Test
+ public void programSelectorFromHalProgramSelector_withValidSelector() {
+ android.hardware.broadcastradio.ProgramSelector halDabSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+ TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+
+ ProgramSelector dabSelector =
+ ConversionUtils.programSelectorFromHalProgramSelector(halDabSelector);
+
+ expect.withMessage("Primary identifier of converted DAB selector")
+ .that(dabSelector.getPrimaryId()).isEqualTo(TEST_DAB_SID_EXT_ID);
+ expect.withMessage("Secondary identifiers of converted DAB selector")
+ .that(dabSelector.getSecondaryIds()).asList()
+ .containsExactly(TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID);
+ }
+
+ @Test
+ public void programSelectorFromHalProgramSelector_withInvalidSelector_returnsNull() {
+ android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{});
+
+ ProgramSelector invalidDabSelector =
+ ConversionUtils.programSelectorFromHalProgramSelector(invalidHalDabSelector);
+
+ expect.withMessage("Invalid DAB selector without required secondary ids")
+ .that(invalidDabSelector).isNull();
+ }
+
+ @Test
+ public void programInfoFromHalProgramInfo_withValidProgramInfo() {
+ android.hardware.broadcastradio.ProgramSelector halDabSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+ TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+ ProgramInfo halProgramInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+ TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+
+ RadioManager.ProgramInfo programInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+
+ expect.withMessage("Primary id of selector of converted program info")
+ .that(programInfo.getSelector().getPrimaryId()).isEqualTo(TEST_DAB_SID_EXT_ID);
+ expect.withMessage("Secondary id of selector of converted program info")
+ .that(programInfo.getSelector().getSecondaryIds()).asList()
+ .containsExactly(TEST_DAB_ENSEMBLE_ID, TEST_DAB_FREQUENCY_ID);
+ expect.withMessage("Logically tuned identifier of converted program info")
+ .that(programInfo.getLogicallyTunedTo()).isEqualTo(TEST_DAB_SID_EXT_ID);
+ expect.withMessage("Physically tuned identifier of converted program info")
+ .that(programInfo.getPhysicallyTunedTo()).isEqualTo(TEST_DAB_FREQUENCY_ID);
+ expect.withMessage("Signal quality of converted program info")
+ .that(programInfo.getSignalStrength()).isEqualTo(TEST_SIGNAL_QUALITY);
+ }
+
+ @Test
+ public void programInfoFromHalProgramInfo_withInvalidDabProgramInfo() {
+ android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID,
+ new ProgramIdentifier[]{TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+ ProgramInfo halProgramInfo = AidlTestUtils.makeHalProgramInfo(invalidHalDabSelector,
+ TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
+
+ RadioManager.ProgramInfo programInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+
+ expect.withMessage("Invalid DAB program info with incorrect type of physically tuned to id")
+ .that(programInfo).isNull();
+ }
+
+ @Test
+ public void chunkFromHalProgramListChunk_withValidChunk() {
+ boolean purge = false;
+ boolean complete = true;
+ android.hardware.broadcastradio.ProgramSelector halDabSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+ TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+ ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+ TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+ RadioManager.ProgramInfo dabInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(halDabInfo);
+ ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete,
+ new ProgramInfo[]{halDabInfo},
+ new ProgramIdentifier[]{TEST_HAL_VENDOR_ID, TEST_HAL_FM_FREQUENCY_ID});
+
+ ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
+
+ expect.withMessage("Purged state of the converted valid program list chunk")
+ .that(chunk.isPurge()).isEqualTo(purge);
+ expect.withMessage("Completion state of the converted valid program list chunk")
+ .that(chunk.isComplete()).isEqualTo(complete);
+ expect.withMessage("Modified program info in the converted valid program list chunk")
+ .that(chunk.getModified()).containsExactly(dabInfo);
+ expect.withMessage("Removed program ides in the converted valid program list chunk")
+ .that(chunk.getRemoved()).containsExactly(TEST_VENDOR_ID, TEST_FM_FREQUENCY_ID);
+ }
+
+ @Test
+ public void chunkFromHalProgramListChunk_withInvalidModifiedProgramInfo() {
+ boolean purge = true;
+ boolean complete = false;
+ android.hardware.broadcastradio.ProgramSelector halDabSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+ TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+ ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+ TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
+ ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete,
+ new ProgramInfo[]{halDabInfo}, new ProgramIdentifier[]{TEST_HAL_FM_FREQUENCY_ID});
+
+ ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
+
+ expect.withMessage("Purged state of the converted invalid program list chunk")
+ .that(chunk.isPurge()).isEqualTo(purge);
+ expect.withMessage("Completion state of the converted invalid program list chunk")
+ .that(chunk.isComplete()).isEqualTo(complete);
+ expect.withMessage("Modified program info in the converted invalid program list chunk")
+ .that(chunk.getModified()).isEmpty();
+ expect.withMessage("Removed program ids in the converted invalid program list chunk")
+ .that(chunk.getRemoved()).containsExactly(TEST_FM_FREQUENCY_ID);
+ }
+
+ @Test
+ public void programSelectorMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
+ expect.withMessage("Selector %s without required SDK version", TEST_DAB_SELECTOR)
+ .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_DAB_SELECTOR,
+ Build.VERSION_CODES.TIRAMISU)).isFalse();
+ }
+
+ @Test
+ public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
+ expect.withMessage("Selector %s with required SDK version", TEST_FM_SELECTOR)
+ .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_FM_SELECTOR,
+ Build.VERSION_CODES.TIRAMISU)).isTrue();
+ }
+
+ @Test
+ public void programInfoMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
+ RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+ TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+
+ expect.withMessage("Program info %s without required SDK version", dabProgramInfo)
+ .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(dabProgramInfo,
+ Build.VERSION_CODES.TIRAMISU)).isFalse();
+ }
+
+ @Test
+ public void programInfoMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
+ RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+ TEST_SIGNAL_QUALITY);
+
+ expect.withMessage("Program info %s with required SDK version", fmProgramInfo)
+ .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(fmProgramInfo,
+ Build.VERSION_CODES.TIRAMISU)).isTrue();
+ }
+
+ @Test
+ public void convertChunkToTargetSdkVersion_withLowerSdkVersion() {
+ RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+ TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+ RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+ TEST_SIGNAL_QUALITY);
+ ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
+ /* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
+ Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+
+ ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
+ Build.VERSION_CODES.TIRAMISU);
+
+ expect.withMessage(
+ "Purged state of the converted program list chunk with lower SDK version")
+ .that(convertedChunk.isPurge()).isEqualTo(chunk.isPurge());
+ expect.withMessage(
+ "Completion state of the converted program list chunk with lower SDK version")
+ .that(convertedChunk.isComplete()).isEqualTo(chunk.isComplete());
+ expect.withMessage(
+ "Modified program info in the converted program list chunk with lower SDK version")
+ .that(convertedChunk.getModified()).containsExactly(fmProgramInfo);
+ expect.withMessage(
+ "Removed program ids in the converted program list chunk with lower SDK version")
+ .that(convertedChunk.getRemoved())
+ .containsExactly(TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID);
+ }
+
+ @Test
+ public void convertChunkToTargetSdkVersion_withRequiredSdkVersion() {
+ RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+ TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+ RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+ TEST_SIGNAL_QUALITY);
+ ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
+ /* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
+ Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+
+ ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+ expect.withMessage("Converted program list chunk with required SDK version")
+ .that(convertedChunk).isEqualTo(chunk);
+ }
+
+ @Test
public void announcementFromHalAnnouncement_typesMatch() {
expect.withMessage("Announcement type")
.that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index c5c6349..d7723ac 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -26,7 +26,9 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,6 +45,8 @@
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
import android.os.Build;
+import android.os.ParcelableException;
+import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -57,7 +61,6 @@
import org.mockito.Mock;
import org.mockito.verification.VerificationWithTimeout;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -71,10 +74,10 @@
timeout(/* millis= */ 200);
private static final int SIGNAL_QUALITY = 1;
private static final long AM_FM_FREQUENCY_SPACING = 500;
- private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+ private static final long[] AM_FM_FREQUENCY_LIST = {97_500, 98_100, 99_100};
private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
- /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
+ /* lowerLimit= */ 87_500, /* upperLimit= */ 108_000, /* spacing= */ 100,
/* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
/* ea= */ false);
private static final RadioManager.BandConfig FM_BAND_CONFIG =
@@ -200,6 +203,17 @@
}
@Test
+ public void setConfiguration_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+ .onConfigurationChanged(FM_BAND_CONFIG);
+ }
+
+ @Test
public void getConfiguration() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
@@ -330,10 +344,17 @@
@Test
public void tune_withUnsupportedSelector_throwsException() throws Exception {
+ ProgramSelector.Identifier dabPrimaryId =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000111L);
+ ProgramSelector.Identifier[] dabSecondaryIds = new ProgramSelector.Identifier[]{
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 1337),
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 225648)};
+ ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+ dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
openAidlClients(/* numClients= */ 1);
- ProgramSelector unsupportedSelector = AidlTestUtils.makeProgramSelector(
- ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
- ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
() -> mTunerSessions[0].tune(unsupportedSelector));
@@ -343,7 +364,22 @@
}
@Test
- public void tune_forCurrentUser_doesNotTune() throws Exception {
+ public void tune_withInvalidSelector_throwsIllegalArgumentException() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector.Identifier invalidDabId = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, /* value= */ 0x1001);
+ ProgramSelector invalidSel = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+ invalidDabId, new ProgramSelector.Identifier[0], new long[0]);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> mTunerSessions[0].tune(invalidSel));
+
+ assertWithMessage("Exception for tuning on DAB selector without DAB_SID_EXT primary id")
+ .that(thrown).hasMessageThat().contains("tune: INVALID_ARGUMENTS");
+ }
+
+ @Test
+ public void tune_forNonCurrentUser_doesNotTune() throws Exception {
openAidlClients(/* numClients= */ 1);
doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
@@ -357,6 +393,21 @@
}
@Test
+ public void tune_withHalHasUnknownError_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector sel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR))
+ .when(mBroadcastRadioMock).tune(any());
+
+ ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+ mTunerSessions[0].tune(sel);
+ });
+
+ assertWithMessage("Exception for tuning when HAL has unknown error")
+ .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
+ }
+
+ @Test
public void step_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[1];
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
@@ -391,6 +442,35 @@
}
@Test
+ public void step_forNonCurrentUser_doesNotStep() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+ .onCurrentProgramInfoChanged(any());
+ }
+
+ @Test
+ public void step_withHalInInvalidState_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doThrow(new ServiceSpecificException(Result.INVALID_STATE))
+ .when(mBroadcastRadioMock).step(anyBoolean());
+
+ IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> {
+ mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+ });
+
+ assertWithMessage("Exception for stepping when HAL is in invalid state")
+ .that(thrown).hasMessageThat().contains("INVALID_STATE");
+ }
+
+ @Test
public void seek_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[2];
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
@@ -432,11 +512,44 @@
ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
.onCurrentProgramInfoChanged(seekUpInfo);
}
@Test
+ public void seek_forNonCurrentUser_doesNotSeek() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
+ AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+ .onCurrentProgramInfoChanged(seekUpInfo);
+ }
+
+ @Test
+ public void seek_withHalHasInternalError_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doThrow(new ServiceSpecificException(Result.INTERNAL_ERROR))
+ .when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+ ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+ mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+ });
+
+ assertWithMessage("Exception for seeking when HAL has internal error")
+ .that(thrown).hasMessageThat().contains("INTERNAL_ERROR");
+ }
+
+ @Test
public void cancel() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
@@ -448,6 +561,32 @@
}
@Test
+ public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ mTunerSessions[0].tune(initialSel);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].cancel();
+
+ verify(mBroadcastRadioMock, never()).cancel();
+ }
+
+ @Test
+ public void cancel_whenHalThrowsRemoteException_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ String exceptionMessage = "HAL service died.";
+ doThrow(new RemoteException(exceptionMessage)).when(mBroadcastRadioMock).cancel();
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mTunerSessions[0].cancel();
+ });
+
+ assertWithMessage("Exception for canceling when HAL throws remote exception")
+ .that(thrown).hasMessageThat().contains(exceptionMessage);
+ }
+
+ @Test
public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = IBroadcastRadio.INVALID_IMAGE;
@@ -471,6 +610,21 @@
}
@Test
+ public void getImage_whenHalThrowsException_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ String exceptionMessage = "HAL service died.";
+ when(mBroadcastRadioMock.getImage(anyInt()))
+ .thenThrow(new RemoteException(exceptionMessage));
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mTunerSessions[0].getImage(/* id= */ 1);
+ });
+
+ assertWithMessage("Exception for getting image when HAL throws remote exception")
+ .that(thrown).hasMessageThat().contains(exceptionMessage);
+ }
+
+ @Test
public void startBackgroundScan() throws Exception {
openAidlClients(/* numClients= */ 1);
@@ -480,6 +634,16 @@
}
@Test
+ public void startBackgroundScan_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].startBackgroundScan();
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onBackgroundScanComplete();
+ }
+
+ @Test
public void stopProgramListUpdates() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
@@ -492,6 +656,19 @@
}
@Test
+ public void stopProgramListUpdates_forNonCurrentUser_doesNotStopUpdates() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ mTunerSessions[0].startProgramListUpdates(aidlFilter);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].stopProgramListUpdates();
+
+ verify(mBroadcastRadioMock, never()).stopProgramListUpdates();
+ }
+
+ @Test
public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
openAidlClients(/* numClients= */ 1);
int flag = UNSUPPORTED_CONFIG_FLAG;
@@ -547,6 +724,17 @@
}
@Test
+ public void setConfigFlag_forNonCurrentUser_doesNotSetConfigFlag() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
+
+ verify(mBroadcastRadioMock, never()).setConfigFlag(flag, /* value= */ true);
+ }
+
+ @Test
public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
throws Exception {
openAidlClients(/* numClients= */ 1);
@@ -556,7 +744,7 @@
mTunerSessions[0].isConfigFlagSet(flag);
});
- assertWithMessage("Exception for check if unsupported flag %s is set", flag)
+ assertWithMessage("Exception for checking if unsupported flag %s is set", flag)
.that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED");
}
@@ -574,6 +762,20 @@
}
@Test
+ public void isConfigFlagSet_whenHalThrowsRemoteException_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+ doThrow(new RemoteException()).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mTunerSessions[0].isConfigFlagSet(flag);
+ });
+
+ assertWithMessage("Exception for checking config flag when HAL throws remote exception")
+ .that(thrown).hasMessageThat().contains("Failed to check flag");
+ }
+
+ @Test
public void setParameters_withMockParameters() throws Exception {
openAidlClients(/* numClients= */ 1);
Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
@@ -586,16 +788,58 @@
}
@Test
+ public void setParameters_forNonCurrentUser_doesNotSetParameters() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+ "mockParam2", "mockValue2");
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].setParameters(parametersSet);
+
+ verify(mBroadcastRadioMock, never()).setParameters(any());
+ }
+
+ @Test
+ public void setParameters_whenHalThrowsRemoteException_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+ "mockParam2", "mockValue2");
+ String exceptionMessage = "HAL service died.";
+ when(mBroadcastRadioMock.setParameters(any()))
+ .thenThrow(new RemoteException(exceptionMessage));
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mTunerSessions[0].setParameters(parametersSet);
+ });
+
+ assertWithMessage("Exception for setting parameters when HAL throws remote exception")
+ .that(thrown).hasMessageThat().contains(exceptionMessage);
+ }
+
+ @Test
public void getParameters_withMockKeys() throws Exception {
openAidlClients(/* numClients= */ 1);
- List<String> parameterKeys = new ArrayList<>(2);
- parameterKeys.add("mockKey1");
- parameterKeys.add("mockKey2");
+ List<String> parameterKeys = List.of("mockKey1", "mockKey2");
mTunerSessions[0].getParameters(parameterKeys);
- verify(mBroadcastRadioMock).getParameters(
- parameterKeys.toArray(new String[0]));
+ verify(mBroadcastRadioMock).getParameters(parameterKeys.toArray(new String[0]));
+ }
+
+ @Test
+ public void getParameters_whenServiceThrowsRemoteException_fails() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ List<String> parameterKeys = List.of("mockKey1", "mockKey2");
+ String exceptionMessage = "HAL service died.";
+ when(mBroadcastRadioMock.getParameters(any()))
+ .thenThrow(new RemoteException(exceptionMessage));
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mTunerSessions[0].getParameters(parameterKeys);
+ });
+
+ assertWithMessage("Exception for getting parameters when HAL throws remote exception")
+ .that(thrown).hasMessageThat().contains(exceptionMessage);
}
@Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index db16c03..6e54dcf 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -324,10 +324,17 @@
@Test
public void tune_withUnsupportedSelector_throwsException() throws Exception {
+ ProgramSelector.Identifier dabPrimaryId =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0xA00111);
+ ProgramSelector.Identifier[] dabSecondaryIds = new ProgramSelector.Identifier[]{
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 1337),
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 225648)};
+ ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+ dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
openAidlClients(/* numClients= */ 1);
- ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector(
- ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
- ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
() -> mTunerSessions[0].tune(unsupportedSelector));
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4011933..9db8805 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -16,6 +16,7 @@
package android.app.activity;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
import static android.content.Intent.ACTION_EDIT;
import static android.content.Intent.ACTION_VIEW;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -204,7 +205,8 @@
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread, null);
- transaction.addCallback(ConfigurationChangeItem.obtain(new Configuration(newConfig)));
+ transaction.addCallback(ConfigurationChangeItem.obtain(
+ new Configuration(newConfig), DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -413,12 +415,14 @@
activity.mTestLatch = new CountDownLatch(1);
ClientTransaction transaction = newTransaction(activityThread, null);
- transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape));
+ transaction.addCallback(ConfigurationChangeItem.obtain(
+ processConfigLandscape, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
transaction = newTransaction(activityThread, activity.getActivityToken());
transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape));
- transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait));
+ transaction.addCallback(ConfigurationChangeItem.obtain(
+ processConfigPortrait, DEVICE_ID_INVALID));
transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
appThread.scheduleTransaction(transaction);
@@ -530,7 +534,7 @@
? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
activityThread.updatePendingConfiguration(newAppConfig);
- activityThread.handleConfigurationChanged(newAppConfig);
+ activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
try {
assertEquals("Virtual display orientation must not change when process"
@@ -548,7 +552,7 @@
private static void restoreConfig(ActivityThread thread, Configuration originalConfig) {
thread.getConfiguration().seq = originalConfig.seq - 1;
ResourcesManager.getInstance().getConfiguration().seq = originalConfig.seq - 1;
- thread.handleConfigurationChanged(originalConfig);
+ thread.handleConfigurationChanged(originalConfig, DEVICE_ID_INVALID);
}
@Test
@@ -626,7 +630,7 @@
newAppConfig.seq++;
final ActivityThread activityThread = activity.getActivityThread();
- activityThread.handleConfigurationChanged(newAppConfig);
+ activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
// Verify that application config update was applied, but didn't change activity config.
assertEquals("Activity config must not change if the process config changes",
diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
index cbf167c..27ee82e 100644
--- a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
@@ -83,6 +83,13 @@
() -> mBackupManager.getBackupRestoreEventLogger(agent));
}
+ @Test
+ public void testGetDelayedRestoreLogger_returnsRestoreLogger() {
+ BackupRestoreEventLogger logger = mBackupManager.getDelayedRestoreLogger();
+
+ assertThat(logger.getOperationType()).isEqualTo(OperationType.RESTORE);
+ }
+
private static BackupAgent getTestAgent() {
return new BackupAgent() {
@Override
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 993ecf6..7f2b51d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -98,15 +98,15 @@
@Test
public void testRecycleConfigurationChangeItem() {
- ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null);
- ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
+ ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0);
+ ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1);
assertNotSame(item, emptyItem);
assertFalse(item.equals(emptyItem));
item.recycle();
assertEquals(item, emptyItem);
- ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config());
+ ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1);
assertSame(item, item2);
assertFalse(item2.equals(emptyItem));
}
@@ -147,6 +147,7 @@
persistableBundle.putInt("k", 4);
IBinder assistToken = new Binder();
IBinder shareableActivityToken = new Binder();
+ int deviceId = 3;
Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
.setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
@@ -155,7 +156,7 @@
.setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
.setIsForward(true).setAssistToken(assistToken)
.setShareableActivityToken(shareableActivityToken)
- .setTaskFragmentToken(new Binder()).build();
+ .setTaskFragmentToken(new Binder()).setDeviceId(deviceId).build();
LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
LaunchActivityItem item = itemSupplier.get();
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 2cd890c..0ed6a29 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -95,6 +95,7 @@
private ActivityInfo mInfo;
private Configuration mCurConfig;
private Configuration mOverrideConfig;
+ private int mDeviceId;
private String mReferrer;
private IVoiceInteractor mVoiceInteractor;
private int mProcState;
@@ -135,6 +136,11 @@
return this;
}
+ LaunchActivityItemBuilder setDeviceId(int deviceId) {
+ mDeviceId = deviceId;
+ return this;
+ }
+
LaunchActivityItemBuilder setReferrer(String referrer) {
mReferrer = referrer;
return this;
@@ -207,7 +213,7 @@
LaunchActivityItem build() {
return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
- mCurConfig, mOverrideConfig, mReferrer, mVoiceInteractor,
+ mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
null /* activityClientController */, mShareableActivityToken,
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index a0ed026..f4bf1cd 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -25,53 +25,26 @@
import static org.junit.Assert.assertTrue;
import android.app.ActivityOptions;
-import android.app.ContentProviderHolder;
-import android.app.IApplicationThread;
-import android.app.IInstrumentationWatcher;
-import android.app.IUiAutomationConnection;
-import android.app.ProfilerInfo;
import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
-import android.content.AutofillOptions;
-import android.content.ComponentName;
-import android.content.ContentCaptureOptions;
-import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ProviderInfoList;
-import android.content.pm.ServiceInfo;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Debug;
-import android.os.IBinder;
import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SharedMemory;
import android.platform.test.annotations.Presubmit;
-import android.view.autofill.AutofillId;
-import android.view.translation.TranslationSpec;
-import android.view.translation.UiTranslationSpec;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.app.IVoiceInteractor;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
/**
* Test parcelling and unparcelling of transactions and transaction items.
@@ -97,7 +70,7 @@
@Test
public void testConfigurationChange() {
// Write to parcel
- ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
+ ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1 /* deviceId */);
writeAndPrepareForReading(item);
// Read from parcel and assert
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index c917302..5ec93e5 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -71,6 +71,7 @@
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -136,7 +137,8 @@
private boolean mImeRequestedShow;
@Override
- public int requestShow(boolean fromController) {
+ public int requestShow(boolean fromController,
+ ImeTracker.Token statsToken) {
if (fromController || mImeRequestedShow) {
mImeRequestedShow = true;
return SHOW_IMMEDIATELY;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 3a3eeee..0486e3c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -43,6 +43,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -221,7 +222,8 @@
return new InsetsSourceConsumer(ITYPE_IME, ime(), state,
() -> mMockTransaction, controller) {
@Override
- public int requestShow(boolean fromController) {
+ public int requestShow(boolean fromController,
+ ImeTracker.Token statsToken) {
return SHOW_IMMEDIATELY;
}
};
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
new file mode 100644
index 0000000..f93cd18
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.StyleSpan;
+import android.text.style.TypefaceSpan;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextAppearanceInfoTest {
+ private static final float EPSILON = 0.0000001f;
+ private static final String TEST_TEXT = "Happy birthday!";
+ private static final float TEXT_SIZE = 16.5f;
+ private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja");
+ private static final String FONT_FAMILY_NAME = "sans-serif";
+ private static final int TEXT_WEIGHT = FontStyle.FONT_WEIGHT_MEDIUM;
+ private static final int TEXT_STYLE = Typeface.ITALIC;
+ private static final boolean ALL_CAPS = true;
+ private static final float SHADOW_DX = 2.0f;
+ private static final float SHADOW_DY = 2.0f;
+ private static final float SHADOW_RADIUS = 2.0f;
+ private static final int SHADOW_COLOR = Color.GRAY;
+ private static final boolean ELEGANT_TEXT_HEIGHT = true;
+ private static final boolean FALLBACK_LINE_SPACING = true;
+ private static final float LETTER_SPACING = 5.0f;
+ private static final String FONT_FEATURE_SETTINGS = "smcp";
+ private static final String FONT_VARIATION_SETTINGS = "'wdth' 1.0";
+ private static final int LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_LOOSE;
+ private static final int LINE_BREAK_WORD_STYLE = LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE;
+ private static final float TEXT_SCALEX = 1.5f;
+ private static final int HIGHLIGHT_TEXT_COLOR = Color.YELLOW;
+ private static final int TEXT_COLOR = Color.RED;
+ private static final int HINT_TEXT_COLOR = Color.GREEN;
+ private static final int LINK_TEXT_COLOR = Color.BLUE;
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private final EditText mEditText = new EditText(mContext);
+ private final SpannableStringBuilder mSpannableText = new SpannableStringBuilder(TEST_TEXT);
+ private Canvas mCanvas;
+
+ @Before
+ public void setUp() {
+ mEditText.setText(mSpannableText);
+ mEditText.getPaint().setTextSize(TEXT_SIZE);
+ mEditText.setTextLocales(TEXT_LOCALES);
+ Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL);
+ mEditText.setTypeface(
+ Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0));
+ mEditText.setAllCaps(ALL_CAPS);
+ mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR);
+ mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT);
+ mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING);
+ mEditText.setLetterSpacing(LETTER_SPACING);
+ mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS);
+ mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS);
+ mEditText.setLineBreakStyle(LINE_BREAK_STYLE);
+ mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE);
+ mEditText.setTextScaleX(TEXT_SCALEX);
+ mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR);
+ mEditText.setTextColor(TEXT_COLOR);
+ mEditText.setHintTextColor(HINT_TEXT_COLOR);
+ mEditText.setLinkTextColor(LINK_TEXT_COLOR);
+ ViewGroup.LayoutParams params =
+ new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ mEditText.setLayoutParams(params);
+ mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ Bitmap bitmap =
+ Bitmap.createBitmap(
+ Math.max(1, mEditText.getMeasuredWidth()),
+ Math.max(1, mEditText.getMeasuredHeight()),
+ Bitmap.Config.ARGB_8888);
+ mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight());
+ mCanvas = new Canvas(bitmap);
+ mEditText.draw(mCanvas);
+ }
+
+ @Test
+ public void testCreateFromTextView_noSpan() {
+ assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText));
+ }
+
+ @Test
+ public void testCreateFromTextView_withSpan1() {
+ AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30);
+ mSpannableText.setSpan(sizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN);
+ mSpannableText.setSpan(colorSpan, 1, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ TypefaceSpan typefaceSpan = new TypefaceSpan("cursive");
+ mSpannableText.setSpan(typefaceSpan, 2, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ mEditText.setText(mSpannableText);
+
+ // |Happy birthday!
+ mEditText.setSelection(0);
+ TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText);
+ assertEquals(info1.getTextSize(), TEXT_SIZE, EPSILON);
+ assertEquals(info1.getTextColor(), TEXT_COLOR);
+ assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+ // H|appy birthday!
+ mEditText.setSelection(1);
+ TextAppearanceInfo info2 = TextAppearanceInfo.createFromTextView(mEditText);
+ assertEquals(info2.getTextSize(), 30f, EPSILON);
+ assertEquals(info2.getTextColor(), TEXT_COLOR);
+ assertEquals(info2.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+ // Ha|ppy birthday!
+ mEditText.setSelection(2);
+ TextAppearanceInfo info3 = TextAppearanceInfo.createFromTextView(mEditText);
+ assertEquals(info3.getTextSize(), 30f, EPSILON);
+ assertEquals(info3.getTextColor(), Color.CYAN);
+ assertEquals(info3.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+ // Ha[ppy birthday!]
+ mEditText.setSelection(2, mSpannableText.length());
+ TextAppearanceInfo info4 = TextAppearanceInfo.createFromTextView(mEditText);
+ assertEquals(info4.getTextSize(), 30f, EPSILON);
+ assertEquals(info4.getTextColor(), Color.CYAN);
+ assertEquals(info4.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+ // Happy| birthday!
+ mEditText.setSelection(5);
+ TextAppearanceInfo info5 = TextAppearanceInfo.createFromTextView(mEditText);
+ assertEquals(info5.getTextSize(), 30f, EPSILON);
+ assertEquals(info5.getTextColor(), Color.CYAN);
+ assertEquals(info5.getSystemFontFamilyName(), "cursive");
+ }
+
+ @Test
+ public void testCreateFromTextView_withSpan2() {
+ // aab|
+ SpannableStringBuilder spannableText = new SpannableStringBuilder("aab");
+
+ AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30);
+ spannableText.setSpan(sizeSpan, 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN);
+ spannableText.setSpan(colorSpan, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
+ spannableText.setSpan(styleSpan, 1, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+
+ TypefaceSpan typefaceSpan = new TypefaceSpan("cursive");
+ spannableText.setSpan(typefaceSpan, 3, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ ScaleXSpan scaleXSpan = new ScaleXSpan(2.0f);
+ spannableText.setSpan(scaleXSpan, 3, 3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ mEditText.setText(spannableText);
+ mEditText.setSelection(3);
+ TextAppearanceInfo info = TextAppearanceInfo.createFromTextView(mEditText);
+
+ // The character before cursor 'b' should only have an AbsoluteSizeSpan.
+ assertEquals(info.getTextSize(), 30f, EPSILON);
+ assertEquals(info.getTextColor(), TEXT_COLOR);
+ assertEquals(info.getTextStyle(), TEXT_STYLE);
+ assertEquals(info.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+ assertEquals(info.getTextScaleX(), TEXT_SCALEX, EPSILON);
+ }
+
+ @Test
+ public void testCreateFromTextView_contradictorySpans() {
+ // Set multiple contradictory spans
+ AbsoluteSizeSpan sizeSpan1 = new AbsoluteSizeSpan(30);
+ CustomForegroundColorSpan colorSpan1 = new CustomForegroundColorSpan(Color.BLUE);
+ AbsoluteSizeSpan sizeSpan2 = new AbsoluteSizeSpan(10);
+ CustomForegroundColorSpan colorSpan2 = new CustomForegroundColorSpan(Color.GREEN);
+
+ mSpannableText.setSpan(sizeSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mSpannableText.setSpan(colorSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mSpannableText.setSpan(sizeSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mSpannableText.setSpan(colorSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ mEditText.setText(mSpannableText);
+ mEditText.draw(mCanvas);
+ mEditText.setSelection(3);
+ // Get a copy of the real TextPaint after setting the last span
+ TextPaint realTextPaint = colorSpan2.lastTextPaint;
+ assertNotNull(realTextPaint);
+ TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText);
+ // Verify the real TextPaint equals the last span of multiple contradictory spans
+ assertEquals(info1.getTextSize(), 10f, EPSILON);
+ assertEquals(info1.getTextSize(), realTextPaint.getTextSize(), EPSILON);
+ assertEquals(info1.getTextColor(), Color.GREEN);
+ assertEquals(info1.getTextColor(), realTextPaint.getColor());
+ assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+ }
+
+ private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) {
+ assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON);
+ assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES);
+ assertEquals(textAppearanceInfo.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+ assertEquals(textAppearanceInfo.getTextFontWeight(), TEXT_WEIGHT);
+ assertEquals(textAppearanceInfo.getTextStyle(), TEXT_STYLE);
+ assertEquals(textAppearanceInfo.isAllCaps(), ALL_CAPS);
+ assertEquals(textAppearanceInfo.getShadowRadius(), SHADOW_RADIUS, EPSILON);
+ assertEquals(textAppearanceInfo.getShadowDx(), SHADOW_DX, EPSILON);
+ assertEquals(textAppearanceInfo.getShadowDy(), SHADOW_DY, EPSILON);
+ assertEquals(textAppearanceInfo.getShadowColor(), SHADOW_COLOR);
+ assertEquals(textAppearanceInfo.isElegantTextHeight(), ELEGANT_TEXT_HEIGHT);
+ assertEquals(textAppearanceInfo.isFallbackLineSpacing(), FALLBACK_LINE_SPACING);
+ assertEquals(textAppearanceInfo.getLetterSpacing(), LETTER_SPACING, EPSILON);
+ assertEquals(textAppearanceInfo.getFontFeatureSettings(), FONT_FEATURE_SETTINGS);
+ assertEquals(textAppearanceInfo.getFontVariationSettings(), FONT_VARIATION_SETTINGS);
+ assertEquals(textAppearanceInfo.getLineBreakStyle(), LINE_BREAK_STYLE);
+ assertEquals(textAppearanceInfo.getLineBreakWordStyle(), LINE_BREAK_WORD_STYLE);
+ assertEquals(textAppearanceInfo.getTextScaleX(), TEXT_SCALEX, EPSILON);
+ assertEquals(textAppearanceInfo.getTextColor(), TEXT_COLOR);
+ assertEquals(textAppearanceInfo.getHighlightTextColor(), HIGHLIGHT_TEXT_COLOR);
+ assertEquals(textAppearanceInfo.getHintTextColor(), HINT_TEXT_COLOR);
+ assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR);
+ }
+
+ static class CustomForegroundColorSpan extends ForegroundColorSpan {
+ @Nullable public TextPaint lastTextPaint = null;
+
+ CustomForegroundColorSpan(int color) {
+ super(color);
+ }
+
+ CustomForegroundColorSpan(@NonNull Parcel src) {
+ super(src);
+ }
+
+ @Override
+ public void updateDrawState(@NonNull TextPaint tp) {
+ super.updateDrawState(tp);
+ // Copy the real TextPaint
+ TextPaint tpCopy = new TextPaint();
+ tpCopy.set(tp);
+ lastTextPaint = tpCopy;
+ }
+ }
+}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 88b2de7..3e75c7d 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -347,7 +347,7 @@
doNothing().when(packageInfo).updateApplicationInfo(any(), any());
return new ActivityClientRecord(mock(IBinder.class), Intent.makeMainActivity(component),
- 0 /* ident */, info, new Configuration(), null /* referrer */,
+ 0 /* ident */, info, new Configuration(), 0 /*deviceId */, null /* referrer */,
null /* voiceInteractor */, null /* state */, null /* persistentState */,
null /* pendingResults */, null /* pendingNewIntents */,
null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bddf452..bc3af1d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -313,6 +313,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1812743677": {
+ "message": "Display id=%d is ignoring all orientation requests, camera is active and the top activity is eligible for force rotation, return %s,portrait activity: %b, is natural orientation portrait: %b.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-1810446914": {
"message": "Trying to update display configuration for system\/invalid process.",
"level": "WARN",
@@ -493,6 +499,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1631991057": {
+ "message": "Display id=%d is notified that Camera %s is closed but activity is still refreshing. Rescheduling an update.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-1630752478": {
"message": "removeLockedTask: removed %s",
"level": "DEBUG",
@@ -637,6 +649,12 @@
"group": "WM_DEBUG_WINDOW_INSETS",
"at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
},
+ "-1480918485": {
+ "message": "Refreshed activity: %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1480772131": {
"message": "No app or window is requesting an orientation, return %d for display id=%d",
"level": "VERBOSE",
@@ -1375,6 +1393,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-799396645": {
+ "message": "Display id=%d is notified that Camera %s is closed, updating rotation.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-799003045": {
"message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
"level": "VERBOSE",
@@ -1603,6 +1627,12 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-627759820": {
+ "message": "Display id=%d is notified that Camera %s is open for package %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-622997754": {
"message": "postWindowRemoveCleanupLocked: %s",
"level": "VERBOSE",
@@ -1633,12 +1663,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-576580969": {
- "message": "viewServerWindowCommand: bootFinished() failed.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-576070986": {
"message": "Performing post-rotate rotation after seamless rotation",
"level": "INFO",
@@ -1981,6 +2005,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-254406860": {
+ "message": "Unable to tell MediaProjectionManagerService about visibility change on the active projection: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-251259736": {
"message": "No longer freezing: %s",
"level": "VERBOSE",
@@ -2161,6 +2191,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/Session.java"
},
+ "-81260230": {
+ "message": "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-81121442": {
"message": "ImeContainer just became organized but it doesn't have a parent or the parent doesn't have a surface control. mSurfaceControl=%s imeParentSurfaceControl=%s",
"level": "ERROR",
@@ -4177,12 +4213,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "1903353011": {
- "message": "notifyAppStopped: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1912291550": {
"message": "Sleep still waiting to pause %s",
"level": "VERBOSE",
@@ -4243,6 +4273,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1967643923": {
+ "message": "Refershing activity for camera compatibility treatment, activityRecord=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"1967975839": {
"message": "Changing app %s visible=%b performLayout=%b",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index e62ac46..2f396c0 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -668,12 +668,21 @@
}
/**
+ * Draws a mesh object to the screen.
+ *
+ * @param mesh {@link Mesh} object that will be drawn to the screen
+ * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader
+ * @param paint {@link Paint} used to provide a color/shader/blend mode.
+ *
* @hide
*/
- public void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+ public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
if (!isHardwareAccelerated() && onHwFeatureInSwMode()) {
throw new RuntimeException("software rendering doesn't support meshes");
}
+ if (blendMode == null) {
+ blendMode = BlendMode.MODULATE;
+ }
nDrawMesh(this.mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
}
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index eeff694..2ec4524 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -607,7 +607,10 @@
}
@Override
- public final void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+ public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
+ if (blendMode == null) {
+ blendMode = BlendMode.MODULATE;
+ }
nDrawMesh(mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
}
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index ef1e7bf..701e20c 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -161,11 +161,17 @@
* be thrown by the decode methods when setting a non-RGB color space
* such as {@link ColorSpace.Named#CIE_LAB Lab}.</p>
*
- * <p class="note">The specified color space's transfer function must be
+ * <p class="note">
+ * Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * the specified color space's transfer function must be
* an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
* <code>IllegalArgumentException</code> will be thrown by the decode methods
* if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
- * specified color space returns null.</p>
+ * specified color space returns null.
+ *
+ * Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * non ICC parametric curve transfer function is allowed.
+ * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.</p>
*
* <p>After decode, the bitmap's color space is stored in
* {@link #outColorSpace}.</p>
@@ -458,7 +464,11 @@
throw new IllegalArgumentException("The destination color space must use the " +
"RGB color model");
}
- if (((ColorSpace.Rgb) opts.inPreferredColorSpace).getTransferParameters() == null) {
+ if (!opts.inPreferredColorSpace.equals(ColorSpace.get(ColorSpace.Named.BT2020_HLG))
+ && !opts.inPreferredColorSpace.equals(
+ ColorSpace.get(ColorSpace.Named.BT2020_PQ))
+ && ((ColorSpace.Rgb) opts.inPreferredColorSpace)
+ .getTransferParameters() == null) {
throw new IllegalArgumentException("The destination color space must use an " +
"ICC parametric transfer function");
}
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 31df474..2427dec 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -199,6 +199,8 @@
private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+ private static final float[] BT2020_PRIMARIES =
+ { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f };
/**
* A gray color space does not have meaningful primaries, so we use this arbitrary set.
*/
@@ -208,6 +210,12 @@
private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+ private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
+ 0.28466892f, 0.5599107f, 0.0f, -3.0f, true);
+ private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f,
+ 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -703,7 +711,29 @@
* <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
* </table>
*/
- CIE_LAB
+ CIE_LAB,
+ /**
+ * <p>{@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+ * Hybrid Log Gamma encoding.</p>
+ * <table summary="Color space definition">
+ * <tr><th>Property</th><th colspan="4">Value</th></tr>
+ * <tr><td>Name</td><td colspan="4">Hybrid Log Gamma encoding</td></tr>
+ * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+ * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+ * </table>
+ */
+ BT2020_HLG,
+ /**
+ * <p>{@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+ * Perceptual Quantizer encoding.</p>
+ * <table summary="Color space definition">
+ * <tr><th>Property</th><th colspan="4">Value</th></tr>
+ * <tr><td>Name</td><td colspan="4">Perceptual Quantizer encoding</td></tr>
+ * <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+ * <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+ * </table>
+ */
+ BT2020_PQ
// Update the initialization block next to #get(Named) when adding new values
}
@@ -1534,7 +1564,7 @@
sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
"Rec. ITU-R BT.2020-1",
- new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
+ BT2020_PRIMARIES,
ILLUMINANT_D65,
null,
new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
@@ -1616,6 +1646,70 @@
"Generic L*a*b*",
Named.CIE_LAB.ordinal()
);
+ sNamedColorSpaces[Named.BT2020_HLG.ordinal()] = new ColorSpace.Rgb(
+ "Hybrid Log Gamma encoding",
+ BT2020_PRIMARIES,
+ ILLUMINANT_D65,
+ null,
+ x -> transferHLGOETF(x),
+ x -> transferHLGEOTF(x),
+ 0.0f, 1.0f,
+ BT2020_HLG_TRANSFER_PARAMETERS,
+ Named.BT2020_HLG.ordinal()
+ );
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_HLG, Named.BT2020_HLG.ordinal());
+ sNamedColorSpaces[Named.BT2020_PQ.ordinal()] = new ColorSpace.Rgb(
+ "Perceptual Quantizer encoding",
+ BT2020_PRIMARIES,
+ ILLUMINANT_D65,
+ null,
+ x -> transferST2048OETF(x),
+ x -> transferST2048EOTF(x),
+ 0.0f, 1.0f,
+ BT2020_PQ_TRANSFER_PARAMETERS,
+ Named.BT2020_PQ.ordinal()
+ );
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_PQ, Named.BT2020_PQ.ordinal());
+ }
+
+ private static double transferHLGOETF(double x) {
+ double a = 0.17883277;
+ double b = 0.28466892;
+ double c = 0.55991073;
+ double r = 0.5;
+ return x > 1.0 ? a * Math.log(x - b) + c : r * Math.sqrt(x);
+ }
+
+ private static double transferHLGEOTF(double x) {
+ double a = 0.17883277;
+ double b = 0.28466892;
+ double c = 0.55991073;
+ double r = 0.5;
+ return x <= 0.5 ? (x * x) / (r * r) : Math.exp((x - c) / a + b);
+ }
+
+ private static double transferST2048OETF(double x) {
+ double m1 = (2610.0 / 4096.0) / 4.0;
+ double m2 = (2523.0 / 4096.0) * 128.0;
+ double c1 = (3424.0 / 4096.0);
+ double c2 = (2413.0 / 4096.0) * 32.0;
+ double c3 = (2392.0 / 4096.0) * 32.0;
+
+ double tmp = Math.pow(x, m1);
+ tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+ return Math.pow(tmp, m2);
+ }
+
+ private static double transferST2048EOTF(double x) {
+ double m1 = (2610.0 / 4096.0) / 4.0;
+ double m2 = (2523.0 / 4096.0) * 128.0;
+ double c1 = (3424.0 / 4096.0);
+ double c2 = (2413.0 / 4096.0) * 32.0;
+ double c3 = (2392.0 / 4096.0) * 32.0;
+
+ double tmp = Math.pow(Math.min(Math.max(x, 0.0), 1.0), 1.0 / m2);
+ tmp = Math.max(tmp - c1, 0.0) / (c2 - c3 * tmp);
+ return Math.pow(tmp, 1.0 / m1);
}
// Reciprocal piecewise gamma response
@@ -2197,6 +2291,58 @@
/** Variable \(g\) in the equation of the EOTF described above. */
public final double g;
+ private TransferParameters(double a, double b, double c, double d, double e,
+ double f, double g, boolean nonCurveTransferParameters) {
+ // nonCurveTransferParameters correspondes to a "special" transfer function
+ if (!nonCurveTransferParameters) {
+ if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)
+ || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f)
+ || Double.isNaN(g)) {
+ throw new IllegalArgumentException("Parameters cannot be NaN");
+ }
+
+ // Next representable float after 1.0
+ // We use doubles here but the representation inside our native code
+ // is often floats
+ if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
+ throw new IllegalArgumentException(
+ "Parameter d must be in the range [0..1], " + "was " + d);
+ }
+
+ if (d == 0.0 && (a == 0.0 || g == 0.0)) {
+ throw new IllegalArgumentException(
+ "Parameter a or g is zero, the transfer function is constant");
+ }
+
+ if (d >= 1.0 && c == 0.0) {
+ throw new IllegalArgumentException(
+ "Parameter c is zero, the transfer function is constant");
+ }
+
+ if ((a == 0.0 || g == 0.0) && c == 0.0) {
+ throw new IllegalArgumentException("Parameter a or g is zero,"
+ + " and c is zero, the transfer function is constant");
+ }
+
+ if (c < 0.0) {
+ throw new IllegalArgumentException(
+ "The transfer function must be increasing");
+ }
+
+ if (a < 0.0 || g < 0.0) {
+ throw new IllegalArgumentException(
+ "The transfer function must be positive or increasing");
+ }
+ }
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ this.g = g;
+ }
+
/**
* <p>Defines the parameters for the ICC parametric curve type 3, as
* defined in ICC.1:2004-10, section 10.15.</p>
@@ -2219,7 +2365,7 @@
* @throws IllegalArgumentException If the parameters form an invalid transfer function
*/
public TransferParameters(double a, double b, double c, double d, double g) {
- this(a, b, c, d, 0.0, 0.0, g);
+ this(a, b, c, d, 0.0, 0.0, g, false);
}
/**
@@ -2238,51 +2384,7 @@
*/
public TransferParameters(double a, double b, double c, double d, double e,
double f, double g) {
-
- if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) ||
- Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) ||
- Double.isNaN(g)) {
- throw new IllegalArgumentException("Parameters cannot be NaN");
- }
-
- // Next representable float after 1.0
- // We use doubles here but the representation inside our native code is often floats
- if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
- throw new IllegalArgumentException("Parameter d must be in the range [0..1], " +
- "was " + d);
- }
-
- if (d == 0.0 && (a == 0.0 || g == 0.0)) {
- throw new IllegalArgumentException(
- "Parameter a or g is zero, the transfer function is constant");
- }
-
- if (d >= 1.0 && c == 0.0) {
- throw new IllegalArgumentException(
- "Parameter c is zero, the transfer function is constant");
- }
-
- if ((a == 0.0 || g == 0.0) && c == 0.0) {
- throw new IllegalArgumentException("Parameter a or g is zero," +
- " and c is zero, the transfer function is constant");
- }
-
- if (c < 0.0) {
- throw new IllegalArgumentException("The transfer function must be increasing");
- }
-
- if (a < 0.0 || g < 0.0) {
- throw new IllegalArgumentException("The transfer function must be " +
- "positive or increasing");
- }
-
- this.a = a;
- this.b = b;
- this.c = c;
- this.d = d;
- this.e = e;
- this.f = f;
- this.g = g;
+ this(a, b, c, d, e, f, g, false);
}
@SuppressWarnings("SimplifiableIfStatement")
@@ -2357,6 +2459,36 @@
private static native long nativeCreate(float a, float b, float c, float d,
float e, float f, float g, float[] xyz);
+ private static DoubleUnaryOperator generateOETF(TransferParameters function) {
+ boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+ || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+ if (isNonCurveTransferParameters) {
+ return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGOETF(x)
+ : x -> transferST2048OETF(x);
+ } else {
+ return function.e == 0.0 && function.f == 0.0
+ ? x -> rcpResponse(x, function.a, function.b,
+ function.c, function.d, function.g)
+ : x -> rcpResponse(x, function.a, function.b, function.c,
+ function.d, function.e, function.f, function.g);
+ }
+ }
+
+ private static DoubleUnaryOperator generateEOTF(TransferParameters function) {
+ boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+ || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+ if (isNonCurveTransferParameters) {
+ return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGEOTF(x)
+ : x -> transferST2048EOTF(x);
+ } else {
+ return function.e == 0.0 && function.f == 0.0
+ ? x -> response(x, function.a, function.b,
+ function.c, function.d, function.g)
+ : x -> response(x, function.a, function.b, function.c,
+ function.d, function.e, function.f, function.g);
+ }
+ }
+
/**
* <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
* The transform matrix must convert from the RGB space to the profile connection
@@ -2553,16 +2685,8 @@
@NonNull TransferParameters function,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
this(name, primaries, whitePoint, transform,
- function.e == 0.0 && function.f == 0.0 ?
- x -> rcpResponse(x, function.a, function.b,
- function.c, function.d, function.g) :
- x -> rcpResponse(x, function.a, function.b, function.c,
- function.d, function.e, function.f, function.g),
- function.e == 0.0 && function.f == 0.0 ?
- x -> response(x, function.a, function.b,
- function.c, function.d, function.g) :
- x -> response(x, function.a, function.b, function.c,
- function.d, function.e, function.f, function.g),
+ generateOETF(function),
+ generateEOTF(function),
0.0f, 1.0f, function, id);
}
@@ -3063,7 +3187,12 @@
*/
@Nullable
public TransferParameters getTransferParameters() {
- return mTransferParameters;
+ if (mTransferParameters != null
+ && !mTransferParameters.equals(BT2020_PQ_TRANSFER_PARAMETERS)
+ && !mTransferParameters.equals(BT2020_HLG_TRANSFER_PARAMETERS)) {
+ return mTransferParameters;
+ }
+ return null;
}
@Override
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 1f693166..e186386 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -16,6 +16,9 @@
package android.graphics;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
import libcore.util.NativeAllocationRegistry;
import java.nio.Buffer;
@@ -25,8 +28,8 @@
* Class representing a mesh object.
*
* This class generates Mesh objects via the
- * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and
- * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
+ * {@link #make(MeshSpecification, int, Buffer, int, Rect)} and
+ * {@link #makeIndexed(MeshSpecification, int, Buffer, int, ShortBuffer, Rect)} methods,
* where a {@link MeshSpecification} is required along with various attributes for
* detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
* for the mesh. Once generated, a mesh object can be drawn through
@@ -39,9 +42,20 @@
private boolean mIsIndexed;
/**
- * Enum to determine how the mesh is represented.
+ * Determines how the mesh is represented and will be drawn.
*/
- public enum Mode {Triangles, TriangleStrip}
+ @IntDef({TRIANGLES, TRIANGLE_STRIP})
+ private @interface Mode {}
+
+ /**
+ * The mesh will be drawn with triangles without utilizing shared vertices.
+ */
+ public static final int TRIANGLES = 0;
+
+ /**
+ * The mesh will be drawn with triangles utilizing shared vertices.
+ */
+ public static final int TRIANGLE_STRIP = 1;
private static class MeshHolder {
public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
@@ -53,7 +67,8 @@
* Generates a {@link Mesh} object.
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
- * @param mode {@link Mode} enum
+ * @param mode Determines what mode to draw the mesh in. Must be one of
+ * {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
* @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
* for all attributes provided within the meshSpec for every vertex. That
* is, a vertex buffer should be (attributes size * number of vertices) in
@@ -63,9 +78,13 @@
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
- public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
- int vertexCount, Rect bounds) {
- long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+ @NonNull
+ public static Mesh make(@NonNull MeshSpecification meshSpec, @Mode int mode,
+ @NonNull Buffer vertexBuffer, int vertexCount, @NonNull Rect bounds) {
+ if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+ throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+ }
+ long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left,
bounds.top, bounds.right, bounds.bottom);
if (nativeMesh == 0) {
@@ -78,7 +97,8 @@
* Generates a {@link Mesh} object.
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
- * @param mode {@link Mode} enum
+ * @param mode Determines what mode to draw the mesh in. Must be one of
+ * {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
* @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
* for all attributes provided within the meshSpec for every vertex. That
* is, a vertex buffer should be (attributes size * number of vertices) in
@@ -92,9 +112,14 @@
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
- public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
- int vertexCount, ShortBuffer indexBuffer, Rect bounds) {
- long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+ @NonNull
+ public static Mesh makeIndexed(@NonNull MeshSpecification meshSpec, @Mode int mode,
+ @NonNull Buffer vertexBuffer, int vertexCount, @NonNull ShortBuffer indexBuffer,
+ @NonNull Rect bounds) {
+ if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+ throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+ }
+ long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer,
indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left,
bounds.top, bounds.right, bounds.bottom);
@@ -114,7 +139,7 @@
* @param color the provided sRGB color will be converted into the shader program's output
* colorspace and be available as a vec4 uniform in the program.
*/
- public void setColorUniform(String uniformName, int color) {
+ public void setColorUniform(@NonNull String uniformName, int color) {
setUniform(uniformName, Color.valueOf(color).getComponents(), true);
}
@@ -128,7 +153,7 @@
* @param color the provided sRGB color will be converted into the shader program's output
* colorspace and be available as a vec4 uniform in the program.
*/
- public void setColorUniform(String uniformName, long color) {
+ public void setColorUniform(@NonNull String uniformName, long color) {
Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
setUniform(uniformName, exSRGB.getComponents(), true);
}
@@ -143,7 +168,7 @@
* @param color the provided sRGB color will be converted into the shader program's output
* colorspace and will be made available as a vec4 uniform in the program.
*/
- public void setColorUniform(String uniformName, Color color) {
+ public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
if (color == null) {
throw new NullPointerException("The color parameter must not be null");
}
@@ -160,7 +185,7 @@
* @param uniformName name matching the float uniform declared in the shader program.
* @param value float value corresponding to the float uniform with the given name.
*/
- public void setFloatUniform(String uniformName, float value) {
+ public void setFloatUniform(@NonNull String uniformName, float value) {
setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
}
@@ -173,7 +198,7 @@
* @param value1 first float value corresponding to the float uniform with the given name.
* @param value2 second float value corresponding to the float uniform with the given name.
*/
- public void setFloatUniform(String uniformName, float value1, float value2) {
+ public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
}
@@ -188,7 +213,8 @@
* @param value3 third float value corresponding to the float unifiform with the given
* name.
*/
- public void setFloatUniform(String uniformName, float value1, float value2, float value3) {
+ public void setFloatUniform(
+ @NonNull String uniformName, float value1, float value2, float value3) {
setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
}
@@ -204,7 +230,7 @@
* @param value4 fourth float value corresponding to the float uniform with the given name.
*/
public void setFloatUniform(
- String uniformName, float value1, float value2, float value3, float value4) {
+ @NonNull String uniformName, float value1, float value2, float value3, float value4) {
setFloatUniform(uniformName, value1, value2, value3, value4, 4);
}
@@ -217,7 +243,7 @@
* @param uniformName name matching the float uniform declared in the shader program.
* @param values float value corresponding to the vec4 float uniform with the given name.
*/
- public void setFloatUniform(String uniformName, float[] values) {
+ public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
setUniform(uniformName, values, false);
}
@@ -249,7 +275,7 @@
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value) {
+ public void setIntUniform(@NonNull String uniformName, int value) {
setIntUniform(uniformName, value, 0, 0, 0, 1);
}
@@ -262,7 +288,7 @@
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value1, int value2) {
+ public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
setIntUniform(uniformName, value1, value2, 0, 0, 2);
}
@@ -276,7 +302,7 @@
* @param value2 second value corresponding to the int uniform with the given name.
* @param value3 third value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value1, int value2, int value3) {
+ public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
setIntUniform(uniformName, value1, value2, value3, 0, 3);
}
@@ -291,7 +317,8 @@
* @param value3 third value corresponding to the int uniform with the given name.
* @param value4 fourth value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) {
+ public void setIntUniform(
+ @NonNull String uniformName, int value1, int value2, int value3, int value4) {
setIntUniform(uniformName, value1, value2, value3, value4, 4);
}
@@ -304,7 +331,7 @@
* @param uniformName name matching the int uniform delcared in the shader program.
* @param values int values corresponding to the vec4 int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int[] values) {
+ public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
if (uniformName == null) {
throw new NullPointerException("The uniformName parameter must not be null");
}
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index dd8fb7a..6ef3596 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -17,15 +17,18 @@
package android.graphics;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import libcore.util.NativeAllocationRegistry;
+import java.util.List;
+
/**
* Class responsible for holding specifications for {@link Mesh} creations. This class
* generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
* the mesh are supplied, including attributes, vertex stride, varyings, and
* vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an {@link AlphaType}.
+ * {@link ColorSpace} as well as an alpha type.
*
* Note that there are several limitations on various mesh specifications:
* 1. The max amount of attributes allowed is 8.
@@ -42,12 +45,11 @@
long mNativeMeshSpec;
/**
- * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+ * Constants for {@link #make(List, int, List, String, String)}
* to determine alpha type. Describes how to interpret the alpha component of a pixel.
*/
@IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
- public @interface AlphaType {
- }
+ private @interface AlphaType {}
/**
* uninitialized.
@@ -73,8 +75,7 @@
* Constants for {@link Attribute} and {@link Varying} for determining the data type.
*/
@IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
- public @interface Type {
- }
+ private @interface Type {}
/**
* Represents one float. Its equivalent shader type is float.
@@ -118,7 +119,7 @@
private int mOffset;
private String mName;
- public Attribute(@Type int type, int offset, String name) {
+ public Attribute(@Type int type, int offset, @NonNull String name) {
mType = type;
mOffset = offset;
mName = name;
@@ -134,7 +135,7 @@
private int mType;
private String mName;
- public Varying(@Type int type, String name) {
+ public Varying(@Type int type, @NonNull String name) {
mType = type;
mName = name;
}
@@ -162,10 +163,13 @@
* @param fragmentShader fragment shader to be supplied to the mesh.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
- public static MeshSpecification make(Attribute[] attributes, int vertexStride,
- Varying[] varyings, String vertexShader, String fragmentShader) {
- long nativeMeshSpec =
- nativeMake(attributes, vertexStride, varyings, vertexShader, fragmentShader);
+ @NonNull
+ public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+ @NonNull List<Varying> varyings, @NonNull String vertexShader,
+ @NonNull String fragmentShader) {
+ long nativeMeshSpec = nativeMake(attributes.toArray(new Attribute[attributes.size()]),
+ vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+ fragmentShader);
if (nativeMeshSpec == 0) {
throw new IllegalArgumentException("MeshSpecification construction failed");
}
@@ -189,9 +193,12 @@
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
- public static MeshSpecification make(Attribute[] attributes, int vertexStride,
- Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace) {
- long nativeMeshSpec = nativeMakeWithCS(attributes, vertexStride, varyings, vertexShader,
+ @NonNull
+ public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+ @NonNull List<Varying> varyings, @NonNull String vertexShader,
+ @NonNull String fragmentShader, @NonNull ColorSpace colorSpace) {
+ long nativeMeshSpec = nativeMakeWithCS(attributes.toArray(new Attribute[attributes.size()]),
+ vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
fragmentShader, colorSpace.getNativeInstance());
if (nativeMeshSpec == 0) {
throw new IllegalArgumentException("MeshSpecification construction failed");
@@ -215,14 +222,22 @@
* @param fragmentShader fragment shader to be supplied to the mesh.
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @param alphaType Describes how to interpret the alpha component for a pixel. Must be
- * one of {@link AlphaType} values.
+ * one of
+ * {@link MeshSpecification#UNKNOWN},
+ * {@link MeshSpecification#OPAQUE},
+ * {@link MeshSpecification#PREMUL}, or
+ * {@link MeshSpecification#UNPREMULT}
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
- public static MeshSpecification make(Attribute[] attributes, int vertexStride,
- Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace,
+ @NonNull
+ public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+ @NonNull List<Varying> varyings, @NonNull String vertexShader,
+ @NonNull String fragmentShader, @NonNull ColorSpace colorSpace,
@AlphaType int alphaType) {
- long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
- fragmentShader, colorSpace.getNativeInstance(), alphaType);
+ long nativeMeshSpec =
+ nativeMakeWithAlpha(attributes.toArray(new Attribute[attributes.size()]),
+ vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+ fragmentShader, colorSpace.getNativeInstance(), alphaType);
if (nativeMeshSpec == 0) {
throw new IllegalArgumentException("MeshSpecification construction failed");
}
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 911315f..75f61f5 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
+ <option name="not-shardable" value="true" />
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp/nativetest" />
<option name="module-name" value="hwui_unit_tests" />
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 6a3bc8f..c835849 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -576,14 +576,22 @@
LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix));
skcms_TransferFunction transferParams;
- // We can only handle numerical transfer functions at the moment
- LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams));
+ decodeColorSpace->transferFn(&transferParams);
+ auto res = skcms_TransferFunction_getType(&transferParams);
+ LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid);
- jobject params = env->NewObject(gTransferParameters_class,
- gTransferParameters_constructorMethodID,
- transferParams.a, transferParams.b, transferParams.c,
- transferParams.d, transferParams.e, transferParams.f,
- transferParams.g);
+ jobject params;
+ if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) {
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c,
+ transferParams.d, transferParams.e, transferParams.f,
+ transferParams.g, true);
+ } else {
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c,
+ transferParams.d, transferParams.e, transferParams.f,
+ transferParams.g, false);
+ }
jfloatArray xyzArray = env->NewFloatArray(9);
jfloat xyz[9] = {
@@ -808,8 +816,8 @@
gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
"android/graphics/ColorSpace$Rgb$TransferParameters"));
- gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class,
- "<init>", "(DDDDDDD)V");
+ gTransferParameters_constructorMethodID =
+ GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDDZ)V");
gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 7b9a93f..3aac48d 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -44,10 +44,16 @@
sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, nullptr, skRect)
- .mesh;
- auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+ auto meshResult = SkMesh::Make(
+ skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+ SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+ if (!meshResult.error.isEmpty()) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+ }
+
+ auto meshPtr = std::make_unique<MeshWrapper>(
+ MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast<jlong>(meshPtr.release());
}
@@ -61,11 +67,17 @@
sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
- skRect)
- .mesh;
- auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+
+ auto meshResult = SkMesh::MakeIndexed(
+ skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+ skIndexBuffer, indexCount, indexOffset,
+ SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+ if (!meshResult.error.isEmpty()) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+ }
+ auto meshPtr = std::make_unique<MeshWrapper>(
+ MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast<jlong>(meshPtr.release());
}
@@ -139,22 +151,22 @@
}
}
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
jfloat value1, jfloat value2, jfloat value3, jfloat value4,
jint count) {
- auto* builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
ScopedUtfChars name(env, uniformName);
const float values[4] = {value1, value2, value3, value4};
- nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+ nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
}
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName,
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
jfloatArray jvalues, jboolean isColor) {
- auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
ScopedUtfChars name(env, jUniformName);
AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
- nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(),
- isColor);
+ nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+ autoValues.length(), isColor);
}
static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -171,20 +183,21 @@
}
}
-static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
jint value1, jint value2, jint value3, jint value4, jint count) {
- auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
ScopedUtfChars name(env, uniformName);
const int values[4] = {value1, value2, value3, value4};
- nativeUpdateIntUniforms(env, builder, name.c_str(), values, count);
+ nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
}
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
jintArray values) {
- auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+ auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
ScopedUtfChars name(env, uniformName);
AutoJavaIntArray autoValues(env, values, 0);
- nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+ nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+ autoValues.length());
}
static void MeshWrapper_destroy(MeshWrapper* wrapper) {
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
index aa014a5..7a73f2d 100644
--- a/libs/hwui/jni/Mesh.h
+++ b/libs/hwui/jni/Mesh.h
@@ -239,6 +239,7 @@
explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
fMeshSpec = sk_sp(meshSpec);
+ fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
}
sk_sp<SkData> fUniforms;
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index 619a3ed..ae9792d 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -78,7 +78,6 @@
auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings,
SkString(skVertexShader.c_str()),
SkString(skFragmentShader.c_str()));
-
if (meshSpecResult.specification.get() == nullptr) {
jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 64839d0..78ae5cf 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -475,6 +475,7 @@
void CanvasContext::notifyFramePending() {
ATRACE_CALL();
mRenderThread.pushBackFrameCallback(this);
+ sendLoadResetHint();
}
void CanvasContext::draw() {
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index a8b0dc2..8b96974 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -37,11 +37,14 @@
* Events are delivered to registered instances of {@link Callback}.
*/
public final class GnssMeasurementsEvent implements Parcelable {
+ private final int mFlag;
private final GnssClock mClock;
private final List<GnssMeasurement> mMeasurements;
private final List<GnssAutomaticGainControl> mGnssAgcs;
private final boolean mIsFullTracking;
+ private static final int HAS_FULL_TRACKING = 1;
+
/**
* Used for receiving GNSS satellite measurements from the GNSS engine.
* Each measurement contains raw and computed data identifying a satellite.
@@ -123,10 +126,12 @@
/**
* Create a {@link GnssMeasurementsEvent} instance with a full list of parameters.
*/
- private GnssMeasurementsEvent(@NonNull GnssClock clock,
+ private GnssMeasurementsEvent(int flag,
+ @NonNull GnssClock clock,
@NonNull List<GnssMeasurement> measurements,
@NonNull List<GnssAutomaticGainControl> agcs,
boolean isFullTracking) {
+ mFlag = flag;
mMeasurements = measurements;
mGnssAgcs = agcs;
mClock = clock;
@@ -168,22 +173,32 @@
*
* False indicates that the GNSS chipset may optimize power via duty cycling, constellations and
* frequency limits, etc.
+ *
+ * <p>The value is only available if {@link #hasFullTracking()} is {@code true}.
*/
- public boolean getIsFullTracking() {
+ public boolean isFullTracking() {
return mIsFullTracking;
}
+ /**
+ * Return {@code true} if {@link #isFullTracking()} is available, {@code false} otherwise.
+ */
+ public boolean hasFullTracking() {
+ return (mFlag & HAS_FULL_TRACKING) == HAS_FULL_TRACKING;
+ }
+
public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR =
new Creator<GnssMeasurementsEvent>() {
@Override
public GnssMeasurementsEvent createFromParcel(Parcel in) {
+ int flag = in.readInt();
GnssClock clock = in.readParcelable(getClass().getClassLoader(),
android.location.GnssClock.class);
List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR);
List<GnssAutomaticGainControl> agcs = in.createTypedArrayList(
GnssAutomaticGainControl.CREATOR);
boolean isFullTracking = in.readBoolean();
- return new GnssMeasurementsEvent(clock, measurements, agcs, isFullTracking);
+ return new GnssMeasurementsEvent(flag, clock, measurements, agcs, isFullTracking);
}
@Override
@@ -199,6 +214,7 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mFlag);
parcel.writeParcelable(mClock, flags);
parcel.writeTypedList(mMeasurements);
parcel.writeTypedList(mGnssAgcs);
@@ -211,13 +227,16 @@
builder.append(mClock);
builder.append(' ').append(mMeasurements.toString());
builder.append(' ').append(mGnssAgcs.toString());
- builder.append(" isFullTracking=").append(mIsFullTracking);
+ if (hasFullTracking()) {
+ builder.append(" isFullTracking=").append(mIsFullTracking);
+ }
builder.append("]");
return builder.toString();
}
/** Builder for {@link GnssMeasurementsEvent} */
public static final class Builder {
+ private int mFlag;
private GnssClock mClock;
private List<GnssMeasurement> mMeasurements;
private List<GnssAutomaticGainControl> mGnssAgcs;
@@ -237,10 +256,11 @@
* {@link GnssMeasurementsEvent}.
*/
public Builder(@NonNull GnssMeasurementsEvent event) {
+ mFlag = event.mFlag;
mClock = event.getClock();
mMeasurements = (List<GnssMeasurement>) event.getMeasurements();
mGnssAgcs = (List<GnssAutomaticGainControl>) event.getGnssAutomaticGainControls();
- mIsFullTracking = event.getIsFullTracking();
+ mIsFullTracking = event.isFullTracking();
}
/**
@@ -313,15 +333,26 @@
* and frequency limits, etc.
*/
@NonNull
- public Builder setIsFullTracking(boolean isFullTracking) {
+ public Builder setFullTracking(boolean isFullTracking) {
+ mFlag |= HAS_FULL_TRACKING;
mIsFullTracking = isFullTracking;
return this;
}
+ /**
+ * Clears the full tracking mode indicator.
+ */
+ @NonNull
+ public Builder clearFullTracking() {
+ mFlag &= ~HAS_FULL_TRACKING;
+ return this;
+ }
+
/** Builds a {@link GnssMeasurementsEvent} instance as specified by this builder. */
@NonNull
public GnssMeasurementsEvent build() {
- return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs, mIsFullTracking);
+ return new GnssMeasurementsEvent(mFlag, mClock, mMeasurements, mGnssAgcs,
+ mIsFullTracking);
}
}
}
diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
index 2c8de2e..147d74c 100644
--- a/media/java/android/media/projection/IMediaProjectionCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -20,4 +20,5 @@
oneway interface IMediaProjectionCallback {
void onStop();
void onCapturedContentResize(int width, int height);
+ void onCapturedContentVisibilityChanged(boolean isVisible);
}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index a63d02b..99d1f8d 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -48,7 +48,11 @@
void notifyActiveProjectionCapturedContentResized(int width, int height);
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
- + ".permission.MANAGE_MEDIA_PROJECTION)")
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
void addCallback(IMediaProjectionWatcherCallback callback);
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 3dfff1f..985ac3c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -66,13 +66,20 @@
}
}
- /** Register a listener to receive notifications about when the {@link
- * MediaProjection} changes state.
+ /**
+ * Register a listener to receive notifications about when the {@link MediaProjection} or
+ * captured content changes state.
+ * <p>
+ * The callback should be registered before invoking
+ * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+ * Handler)}
+ * to ensure that any notifications on the callback are not missed.
+ * </p>
*
* @param callback The callback to call.
- * @param handler The handler on which the callback should be invoked, or
- * null if the callback should be invoked on the calling thread's looper.
- *
+ * @param handler The handler on which the callback should be invoked, or
+ * null if the callback should be invoked on the calling thread's looper.
+ * @throws IllegalArgumentException If the given callback is null.
* @see #unregisterCallback
*/
public void registerCallback(Callback callback, Handler handler) {
@@ -85,10 +92,11 @@
mCallbacks.put(callback, new CallbackRecord(callback, handler));
}
- /** Unregister a MediaProjection listener.
+ /**
+ * Unregister a {@link MediaProjection} listener.
*
* @param callback The callback to unregister.
- *
+ * @throws IllegalArgumentException If the given callback is null.
* @see #registerCallback
*/
public void unregisterCallback(Callback callback) {
@@ -283,6 +291,34 @@
* }</pre>
*/
public void onCapturedContentResize(int width, int height) { }
+
+ /**
+ * Indicates the visibility of the captured region has changed. Called immediately after
+ * capture begins with the initial visibility state, and when visibility changes. Provides
+ * the app with accurate state for presenting its own UI. The application can take advantage
+ * of this by showing or hiding the captured content, based on if the captured region is
+ * currently visible to the user.
+ * <p>
+ * For example, if the user elected to capture a single app (from the activity shown from
+ * {@link MediaProjectionManager#createScreenCaptureIntent()}), the callback may be
+ * triggered for the following reasons:
+ * <ul>
+ * <li>
+ * The captured region may become visible ({@code isVisible} with value
+ * {@code true}), because the captured app is at least partially visible. This may
+ * happen if the captured app was previously covered by another app. The other app
+ * moves to show at least some portion of the captured app.
+ * </li>
+ * <li>
+ * The captured region may become invisible ({@code isVisible} with value
+ * {@code false}) if it is entirely hidden. This may happen if the captured app is
+ * entirely covered by another app, or the user navigates away from the captured
+ * app.
+ * </li>
+ * </ul>
+ * </p>
+ */
+ public void onCapturedContentVisibilityChanged(boolean isVisible) { }
}
private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@@ -299,6 +335,13 @@
cbr.onCapturedContentResize(width, height);
}
}
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ for (CallbackRecord cbr : mCallbacks.values()) {
+ cbr.onCapturedContentVisibilityChanged(isVisible);
+ }
+ }
}
private final static class CallbackRecord {
@@ -322,5 +365,9 @@
public void onCapturedContentResize(int width, int height) {
mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
}
+
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible));
+ }
}
}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index e60d537..667a9ae 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -946,6 +946,10 @@
id = generateInputId(componentName, mTvInputHardwareInfo);
type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
isHardwareInput = true;
+ if (mTvInputHardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
+ mHdmiDeviceInfo = HdmiDeviceInfo.hardwarePort(
+ HdmiDeviceInfo.PATH_INVALID, mTvInputHardwareInfo.getHdmiPortId());
+ }
} else {
id = generateInputId(componentName);
type = TYPE_TUNER;
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 7963ff2..63292ce 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -20,4 +20,5 @@
platform_apis: true,
certificate: "platform",
resource_dirs: ["res"],
+ test_suites: ["device-tests"],
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index eeb8368..b7fb294 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -214,17 +214,21 @@
GetCredentialProviderData.Builder("io.enpass.app")
.setCredentialEntries(
listOf<Entry>(
+ newGetEntry(
+ "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
+ "elisa.family@outlook.com", null, 3L
+ ),
newGetEntry(
"key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
- "elisa.bakery@gmail.com", "Elisa Beckett", 300L
+ "elisa.bakery@gmail.com", "Elisa Beckett", 0L
),
newGetEntry(
"key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
- "elisa.bakery@gmail.com", null, 300L
+ "elisa.bakery@gmail.com", null, 10L
),
newGetEntry(
- "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
- "elisa.family@outlook.com", null, 100L
+ "key1", "subkey-3", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+ "elisa.family@outlook.com", "Elisa Beckett", 1L
),
)
).setAuthenticationEntry(
@@ -247,12 +251,12 @@
.setCredentialEntries(
listOf<Entry>(
newGetEntry(
- "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
- "elisa.family@outlook.com", null, 600L
+ "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
+ "elisa.family@outlook.com", null, 4L
),
newGetEntry(
- "key1", "subkey-2", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
- "elisa.family@outlook.com", null, 100L
+ "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
+ "elisa.work@outlook.com", null, 11L
),
)
).setAuthenticationEntry(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 5e7f1e0..ac0db5a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -146,13 +146,19 @@
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineSmall,
text = stringResource(
- if (sortedUserNameToCredentialEntryList.size == 1) {
- if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
- .first().credentialType
+ if (sortedUserNameToCredentialEntryList
+ .size == 1 && authenticationEntryList.isEmpty()
+ ) {
+ if (sortedUserNameToCredentialEntryList.first()
+ .sortedCredentialEntryList.first().credentialType
== PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
- )
- R.string.get_dialog_title_use_passkey_for
+ ) R.string.get_dialog_title_use_passkey_for
else R.string.get_dialog_title_use_sign_in_for
+ } else if (
+ sortedUserNameToCredentialEntryList
+ .isEmpty() && authenticationEntryList.size == 1
+ ) {
+ R.string.get_dialog_title_use_sign_in_for
} else R.string.get_dialog_title_choose_sign_in_for,
requestDisplayInfo.appDomainName
),
@@ -164,20 +170,46 @@
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
) {
+ val usernameForCredentialSize = sortedUserNameToCredentialEntryList
+ .size
+ val authenticationEntrySize = authenticationEntryList.size
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
- items(sortedUserNameToCredentialEntryList) {
- CredentialEntryRow(
- credentialEntryInfo = it.sortedCredentialEntryList.first(),
- onEntrySelected = onEntrySelected,
- )
- }
- items(authenticationEntryList) {
- AuthenticationEntryRow(
- authenticationEntryInfo = it,
- onEntrySelected = onEntrySelected,
- )
+ // Show max 4 entries in this primary page
+ if (usernameForCredentialSize + authenticationEntrySize <= 4) {
+ items(sortedUserNameToCredentialEntryList) {
+ CredentialEntryRow(
+ credentialEntryInfo = it.sortedCredentialEntryList.first(),
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ items(authenticationEntryList) {
+ AuthenticationEntryRow(
+ authenticationEntryInfo = it,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ } else if (usernameForCredentialSize < 4) {
+ items(sortedUserNameToCredentialEntryList) {
+ CredentialEntryRow(
+ credentialEntryInfo = it.sortedCredentialEntryList.first(),
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ items(authenticationEntryList.take(4 - usernameForCredentialSize)) {
+ AuthenticationEntryRow(
+ authenticationEntryInfo = it,
+ onEntrySelected = onEntrySelected,
+ )
+ }
+ } else {
+ items(sortedUserNameToCredentialEntryList.take(4)) {
+ CredentialEntryRow(
+ credentialEntryInfo = it.sortedCredentialEntryList.first(),
+ onEntrySelected = onEntrySelected,
+ )
+ }
}
}
}
@@ -257,7 +289,7 @@
)
}
// Locked password manager
- if (!authenticationEntryList.isEmpty()) {
+ if (authenticationEntryList.isNotEmpty()) {
item {
LockedCredentials(
authenticationEntryList = authenticationEntryList,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index c182397..294e468 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -182,7 +182,7 @@
Preconditions.checkState(remoteEntryList.size <= 1)
// Compose sortedUserNameToCredentialEntryList
- val comparator = CredentialEntryInfoComparator()
+ val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
// Sort per username
userNameToCredentialEntryMap.values.forEach {
it.sortWith(comparator)
@@ -191,7 +191,7 @@
val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
PerUserNameCredentialEntryList(it.key, it.value)
}.sortedWith(
- compareBy(comparator) { it.sortedCredentialEntryList.first() }
+ compareByDescending{ it.sortedCredentialEntryList.first().lastUsedTimeMillis }
)
return ProviderDisplayInfo(
@@ -219,7 +219,7 @@
GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
}
-internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
+internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> {
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
// First prefer passkey type for its security benefits
if (p0.credentialType != p1.credentialType) {
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 976a279..88bb30b 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -1,190 +1,328 @@
<?xml version="1.0" encoding="utf-8"?>
<keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
- <keyboard-layout android:name="keyboard_layout_english_uk"
- android:label="@string/keyboard_layout_english_uk_label"
- android:keyboardLayout="@raw/keyboard_layout_english_uk" />
+ <keyboard-layout
+ android:name="keyboard_layout_english_uk"
+ android:label="@string/keyboard_layout_english_uk_label"
+ android:keyboardLayout="@raw/keyboard_layout_english_uk"
+ android:keyboardLocale="en-Latn-GB"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_english_us"
- android:label="@string/keyboard_layout_english_us_label"
- android:keyboardLayout="@raw/keyboard_layout_english_us" />
+ <keyboard-layout
+ android:name="keyboard_layout_english_us"
+ android:label="@string/keyboard_layout_english_us_label"
+ android:keyboardLayout="@raw/keyboard_layout_english_us"
+ android:keyboardLocale="en-Latn,en-Latn-US"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_english_us_intl"
- android:label="@string/keyboard_layout_english_us_intl"
- android:keyboardLayout="@raw/keyboard_layout_english_us_intl" />
+ <keyboard-layout
+ android:name="keyboard_layout_english_us_intl"
+ android:label="@string/keyboard_layout_english_us_intl"
+ android:keyboardLayout="@raw/keyboard_layout_english_us_intl"
+ android:keyboardLocale="en-Latn-US"
+ android:keyboardLayoutType="extended" />
- <keyboard-layout android:name="keyboard_layout_english_us_colemak"
- android:label="@string/keyboard_layout_english_us_colemak_label"
- android:keyboardLayout="@raw/keyboard_layout_english_us_colemak" />
+ <keyboard-layout
+ android:name="keyboard_layout_english_us_colemak"
+ android:label="@string/keyboard_layout_english_us_colemak_label"
+ android:keyboardLayout="@raw/keyboard_layout_english_us_colemak"
+ android:keyboardLocale="en-Latn-US"
+ android:keyboardLayoutType="colemak" />
- <keyboard-layout android:name="keyboard_layout_english_us_dvorak"
- android:label="@string/keyboard_layout_english_us_dvorak_label"
- android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak" />
+ <keyboard-layout
+ android:name="keyboard_layout_english_us_dvorak"
+ android:label="@string/keyboard_layout_english_us_dvorak_label"
+ android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak"
+ android:keyboardLocale="en-Latn-US"
+ android:keyboardLayoutType="dvorak" />
- <keyboard-layout android:name="keyboard_layout_english_us_workman"
- android:label="@string/keyboard_layout_english_us_workman_label"
- android:keyboardLayout="@raw/keyboard_layout_english_us_workman" />
+ <keyboard-layout
+ android:name="keyboard_layout_english_us_workman"
+ android:label="@string/keyboard_layout_english_us_workman_label"
+ android:keyboardLayout="@raw/keyboard_layout_english_us_workman"
+ android:keyboardLocale="en-Latn-US"
+ android:keyboardLayoutType="workman" />
- <keyboard-layout android:name="keyboard_layout_german"
- android:label="@string/keyboard_layout_german_label"
- android:keyboardLayout="@raw/keyboard_layout_german" />
+ <keyboard-layout
+ android:name="keyboard_layout_german"
+ android:label="@string/keyboard_layout_german_label"
+ android:keyboardLayout="@raw/keyboard_layout_german"
+ android:keyboardLocale="de-Latn"
+ android:keyboardLayoutType="qwertz" />
- <keyboard-layout android:name="keyboard_layout_french"
- android:label="@string/keyboard_layout_french_label"
- android:keyboardLayout="@raw/keyboard_layout_french" />
+ <keyboard-layout
+ android:name="keyboard_layout_french"
+ android:label="@string/keyboard_layout_french_label"
+ android:keyboardLayout="@raw/keyboard_layout_french"
+ android:keyboardLocale="fr-Latn-FR"
+ android:keyboardLayoutType="azerty" />
- <keyboard-layout android:name="keyboard_layout_french_ca"
- android:label="@string/keyboard_layout_french_ca_label"
- android:keyboardLayout="@raw/keyboard_layout_french_ca" />
+ <keyboard-layout
+ android:name="keyboard_layout_french_ca"
+ android:label="@string/keyboard_layout_french_ca_label"
+ android:keyboardLayout="@raw/keyboard_layout_french_ca"
+ android:keyboardLocale="fr-Latn-CA"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_russian"
- android:label="@string/keyboard_layout_russian_label"
- android:keyboardLayout="@raw/keyboard_layout_russian" />
+ <keyboard-layout
+ android:name="keyboard_layout_russian"
+ android:label="@string/keyboard_layout_russian_label"
+ android:keyboardLayout="@raw/keyboard_layout_russian"
+ android:keyboardLocale="ru-Cyrl" />
- <keyboard-layout android:name="keyboard_layout_russian_mac"
- android:label="@string/keyboard_layout_russian_mac_label"
- android:keyboardLayout="@raw/keyboard_layout_russian_mac" />
+ <keyboard-layout
+ android:name="keyboard_layout_russian_mac"
+ android:label="@string/keyboard_layout_russian_mac_label"
+ android:keyboardLayout="@raw/keyboard_layout_russian_mac"
+ android:keyboardLocale="ru-Cyrl"
+ android:keyboardLayoutType="extended" />
- <keyboard-layout android:name="keyboard_layout_spanish"
- android:label="@string/keyboard_layout_spanish_label"
- android:keyboardLayout="@raw/keyboard_layout_spanish" />
+ <keyboard-layout
+ android:name="keyboard_layout_spanish"
+ android:label="@string/keyboard_layout_spanish_label"
+ android:keyboardLayout="@raw/keyboard_layout_spanish"
+ android:keyboardLocale="es-Latn-ES"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_swiss_french"
- android:label="@string/keyboard_layout_swiss_french_label"
- android:keyboardLayout="@raw/keyboard_layout_swiss_french" />
+ <keyboard-layout
+ android:name="keyboard_layout_swiss_french"
+ android:label="@string/keyboard_layout_swiss_french_label"
+ android:keyboardLayout="@raw/keyboard_layout_swiss_french"
+ android:keyboardLocale="fr-Latn-CH"
+ android:keyboardLayoutType="qwertz" />
- <keyboard-layout android:name="keyboard_layout_swiss_german"
- android:label="@string/keyboard_layout_swiss_german_label"
- android:keyboardLayout="@raw/keyboard_layout_swiss_german" />
+ <keyboard-layout
+ android:name="keyboard_layout_swiss_german"
+ android:label="@string/keyboard_layout_swiss_german_label"
+ android:keyboardLayout="@raw/keyboard_layout_swiss_german"
+ android:keyboardLocale="de-Latn-CH"
+ android:keyboardLayoutType="qwertz" />
- <keyboard-layout android:name="keyboard_layout_belgian"
- android:label="@string/keyboard_layout_belgian"
- android:keyboardLayout="@raw/keyboard_layout_belgian" />
+ <keyboard-layout
+ android:name="keyboard_layout_belgian"
+ android:label="@string/keyboard_layout_belgian"
+ android:keyboardLayout="@raw/keyboard_layout_belgian"
+ android:keyboardLocale="fr-Latn-BE"
+ android:keyboardLayoutType="azerty" />
- <keyboard-layout android:name="keyboard_layout_bulgarian"
- android:label="@string/keyboard_layout_bulgarian"
- android:keyboardLayout="@raw/keyboard_layout_bulgarian" />
+ <keyboard-layout
+ android:name="keyboard_layout_bulgarian"
+ android:label="@string/keyboard_layout_bulgarian"
+ android:keyboardLayout="@raw/keyboard_layout_bulgarian"
+ android:keyboardLocale="bg-Cyrl" />
- <keyboard-layout android:name="keyboard_layout_bulgarian_phonetic"
- android:label="@string/keyboard_layout_bulgarian_phonetic"
- android:keyboardLayout="@raw/keyboard_layout_bulgarian_phonetic" />
+ <keyboard-layout
+ android:name="keyboard_layout_bulgarian_phonetic"
+ android:label="@string/keyboard_layout_bulgarian_phonetic"
+ android:keyboardLayout="@raw/keyboard_layout_bulgarian_phonetic"
+ android:keyboardLocale="bg-Cyrl"
+ android:keyboardLayoutType="extended" />
- <keyboard-layout android:name="keyboard_layout_italian"
- android:label="@string/keyboard_layout_italian"
- android:keyboardLayout="@raw/keyboard_layout_italian" />
+ <keyboard-layout
+ android:name="keyboard_layout_italian"
+ android:label="@string/keyboard_layout_italian"
+ android:keyboardLayout="@raw/keyboard_layout_italian"
+ android:keyboardLocale="it-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_danish"
- android:label="@string/keyboard_layout_danish"
- android:keyboardLayout="@raw/keyboard_layout_danish" />
+ <keyboard-layout
+ android:name="keyboard_layout_danish"
+ android:label="@string/keyboard_layout_danish"
+ android:keyboardLayout="@raw/keyboard_layout_danish"
+ android:keyboardLocale="da-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_norwegian"
- android:label="@string/keyboard_layout_norwegian"
- android:keyboardLayout="@raw/keyboard_layout_norwegian" />
+ <keyboard-layout
+ android:name="keyboard_layout_norwegian"
+ android:label="@string/keyboard_layout_norwegian"
+ android:keyboardLayout="@raw/keyboard_layout_norwegian"
+ android:keyboardLocale="nb-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_swedish"
- android:label="@string/keyboard_layout_swedish"
- android:keyboardLayout="@raw/keyboard_layout_swedish" />
+ <keyboard-layout
+ android:name="keyboard_layout_swedish"
+ android:label="@string/keyboard_layout_swedish"
+ android:keyboardLayout="@raw/keyboard_layout_swedish"
+ android:keyboardLocale="sv-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_finnish"
- android:label="@string/keyboard_layout_finnish"
- android:keyboardLayout="@raw/keyboard_layout_finnish" />
+ <keyboard-layout
+ android:name="keyboard_layout_finnish"
+ android:label="@string/keyboard_layout_finnish"
+ android:keyboardLayout="@raw/keyboard_layout_finnish"
+ android:keyboardLocale="fi-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_croatian"
- android:label="@string/keyboard_layout_croatian"
- android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian" />
+ <keyboard-layout
+ android:name="keyboard_layout_croatian"
+ android:label="@string/keyboard_layout_croatian"
+ android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian"
+ android:keyboardLocale="hr-Latn"
+ android:keyboardLayoutType="qwertz" />
- <keyboard-layout android:name="keyboard_layout_czech"
- android:label="@string/keyboard_layout_czech"
- android:keyboardLayout="@raw/keyboard_layout_czech" />
+ <keyboard-layout
+ android:name="keyboard_layout_czech"
+ android:label="@string/keyboard_layout_czech"
+ android:keyboardLayout="@raw/keyboard_layout_czech"
+ android:keyboardLocale="cs-Latn"
+ android:keyboardLayoutType="qwertz" />
- <keyboard-layout android:name="keyboard_layout_czech_qwerty"
- android:label="@string/keyboard_layout_czech_qwerty"
- android:keyboardLayout="@raw/keyboard_layout_czech_qwerty" />
+ <keyboard-layout
+ android:name="keyboard_layout_czech_qwerty"
+ android:label="@string/keyboard_layout_czech_qwerty"
+ android:keyboardLayout="@raw/keyboard_layout_czech_qwerty"
+ android:keyboardLocale="cs-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_estonian"
- android:label="@string/keyboard_layout_estonian"
- android:keyboardLayout="@raw/keyboard_layout_estonian" />
+ <keyboard-layout
+ android:name="keyboard_layout_estonian"
+ android:label="@string/keyboard_layout_estonian"
+ android:keyboardLayout="@raw/keyboard_layout_estonian"
+ android:keyboardLocale="et-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_hungarian"
- android:label="@string/keyboard_layout_hungarian"
- android:keyboardLayout="@raw/keyboard_layout_hungarian" />
+ <keyboard-layout
+ android:name="keyboard_layout_hungarian"
+ android:label="@string/keyboard_layout_hungarian"
+ android:keyboardLayout="@raw/keyboard_layout_hungarian"
+ android:keyboardLocale="hu-Latn"
+ android:keyboardLayoutType="qwertz" />
- <keyboard-layout android:name="keyboard_layout_icelandic"
- android:label="@string/keyboard_layout_icelandic"
- android:keyboardLayout="@raw/keyboard_layout_icelandic" />
+ <keyboard-layout
+ android:name="keyboard_layout_icelandic"
+ android:label="@string/keyboard_layout_icelandic"
+ android:keyboardLayout="@raw/keyboard_layout_icelandic"
+ android:keyboardLocale="is-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_brazilian"
- android:label="@string/keyboard_layout_brazilian"
- android:keyboardLayout="@raw/keyboard_layout_brazilian" />
+ <keyboard-layout
+ android:name="keyboard_layout_brazilian"
+ android:label="@string/keyboard_layout_brazilian"
+ android:keyboardLayout="@raw/keyboard_layout_brazilian"
+ android:keyboardLocale="pt-Latn-BR"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_portuguese"
- android:label="@string/keyboard_layout_portuguese"
- android:keyboardLayout="@raw/keyboard_layout_portuguese" />
+ <keyboard-layout
+ android:name="keyboard_layout_portuguese"
+ android:label="@string/keyboard_layout_portuguese"
+ android:keyboardLayout="@raw/keyboard_layout_portuguese"
+ android:keyboardLocale="pt-Latn-PT"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_slovak"
- android:label="@string/keyboard_layout_slovak"
- android:keyboardLayout="@raw/keyboard_layout_slovak" />
+ <keyboard-layout
+ android:name="keyboard_layout_slovak"
+ android:label="@string/keyboard_layout_slovak"
+ android:keyboardLayout="@raw/keyboard_layout_slovak"
+ android:keyboardLocale="sk-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_slovenian"
- android:label="@string/keyboard_layout_slovenian"
- android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian" />
+ <keyboard-layout
+ android:name="keyboard_layout_slovenian"
+ android:label="@string/keyboard_layout_slovenian"
+ android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian"
+ android:keyboardLocale="sl-Latn"
+ android:keyboardLayoutType="qwertz" />
- <keyboard-layout android:name="keyboard_layout_turkish"
- android:label="@string/keyboard_layout_turkish"
- android:keyboardLayout="@raw/keyboard_layout_turkish" />
+ <keyboard-layout
+ android:name="keyboard_layout_turkish"
+ android:label="@string/keyboard_layout_turkish"
+ android:keyboardLayout="@raw/keyboard_layout_turkish"
+ android:keyboardLocale="tr-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_turkish_f"
- android:label="@string/keyboard_layout_turkish_f"
- android:keyboardLayout="@raw/keyboard_layout_turkish_f" />
+ <keyboard-layout
+ android:name="keyboard_layout_turkish"
+ android:label="@string/keyboard_layout_turkish"
+ android:keyboardLayout="@raw/keyboard_layout_turkish"
+ android:keyboardLocale="tr-Latn"
+ android:keyboardLayoutType="turkish_q" />
- <keyboard-layout android:name="keyboard_layout_ukrainian"
- android:label="@string/keyboard_layout_ukrainian"
- android:keyboardLayout="@raw/keyboard_layout_ukrainian" />
+ <keyboard-layout
+ android:name="keyboard_layout_turkish_f"
+ android:label="@string/keyboard_layout_turkish_f"
+ android:keyboardLayout="@raw/keyboard_layout_turkish_f"
+ android:keyboardLocale="tr-Latn"
+ android:keyboardLayoutType="turkish_f" />
- <keyboard-layout android:name="keyboard_layout_arabic"
- android:label="@string/keyboard_layout_arabic"
- android:keyboardLayout="@raw/keyboard_layout_arabic" />
+ <keyboard-layout
+ android:name="keyboard_layout_ukrainian"
+ android:label="@string/keyboard_layout_ukrainian"
+ android:keyboardLayout="@raw/keyboard_layout_ukrainian"
+ android:keyboardLocale="uk-Cyrl" />
- <keyboard-layout android:name="keyboard_layout_greek"
- android:label="@string/keyboard_layout_greek"
- android:keyboardLayout="@raw/keyboard_layout_greek" />
+ <keyboard-layout
+ android:name="keyboard_layout_arabic"
+ android:label="@string/keyboard_layout_arabic"
+ android:keyboardLayout="@raw/keyboard_layout_arabic"
+ android:keyboardLocale="ar-Arab" />
- <keyboard-layout android:name="keyboard_layout_hebrew"
- android:label="@string/keyboard_layout_hebrew"
- android:keyboardLayout="@raw/keyboard_layout_hebrew" />
+ <keyboard-layout
+ android:name="keyboard_layout_greek"
+ android:label="@string/keyboard_layout_greek"
+ android:keyboardLayout="@raw/keyboard_layout_greek"
+ android:keyboardLocale="el-Grek" />
- <keyboard-layout android:name="keyboard_layout_lithuanian"
- android:label="@string/keyboard_layout_lithuanian"
- android:keyboardLayout="@raw/keyboard_layout_lithuanian" />
+ <keyboard-layout
+ android:name="keyboard_layout_hebrew"
+ android:label="@string/keyboard_layout_hebrew"
+ android:keyboardLayout="@raw/keyboard_layout_hebrew"
+ android:keyboardLocale="iw-Hebr" />
- <keyboard-layout android:name="keyboard_layout_spanish_latin"
- android:label="@string/keyboard_layout_spanish_latin"
- android:keyboardLayout="@raw/keyboard_layout_spanish_latin" />
+ <keyboard-layout
+ android:name="keyboard_layout_lithuanian"
+ android:label="@string/keyboard_layout_lithuanian"
+ android:keyboardLayout="@raw/keyboard_layout_lithuanian"
+ android:keyboardLocale="lt-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_latvian"
- android:label="@string/keyboard_layout_latvian"
- android:keyboardLayout="@raw/keyboard_layout_latvian_qwerty" />
+ <keyboard-layout
+ android:name="keyboard_layout_spanish_latin"
+ android:label="@string/keyboard_layout_spanish_latin"
+ android:keyboardLayout="@raw/keyboard_layout_spanish_latin"
+ android:keyboardLocale="es-Latn-419"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_persian"
- android:label="@string/keyboard_layout_persian"
- android:keyboardLayout="@raw/keyboard_layout_persian" />
+ <keyboard-layout
+ android:name="keyboard_layout_latvian"
+ android:label="@string/keyboard_layout_latvian"
+ android:keyboardLayout="@raw/keyboard_layout_latvian_qwerty"
+ android:keyboardLocale="lv-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_azerbaijani"
- android:label="@string/keyboard_layout_azerbaijani"
- android:keyboardLayout="@raw/keyboard_layout_azerbaijani" />
+ <keyboard-layout
+ android:name="keyboard_layout_persian"
+ android:label="@string/keyboard_layout_persian"
+ android:keyboardLayout="@raw/keyboard_layout_persian"
+ android:keyboardLocale="fa-Arab" />
- <keyboard-layout android:name="keyboard_layout_polish"
- android:label="@string/keyboard_layout_polish"
- android:keyboardLayout="@raw/keyboard_layout_polish" />
+ <keyboard-layout
+ android:name="keyboard_layout_azerbaijani"
+ android:label="@string/keyboard_layout_azerbaijani"
+ android:keyboardLayout="@raw/keyboard_layout_azerbaijani"
+ android:keyboardLocale="az-Latn-AZ"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_belarusian"
- android:label="@string/keyboard_layout_belarusian"
- android:keyboardLayout="@raw/keyboard_layout_belarusian" />
+ <keyboard-layout
+ android:name="keyboard_layout_polish"
+ android:label="@string/keyboard_layout_polish"
+ android:keyboardLayout="@raw/keyboard_layout_polish"
+ android:keyboardLocale="pl-Latn"
+ android:keyboardLayoutType="qwerty" />
- <keyboard-layout android:name="keyboard_layout_mongolian"
- android:label="@string/keyboard_layout_mongolian"
- android:keyboardLayout="@raw/keyboard_layout_mongolian" />
+ <keyboard-layout
+ android:name="keyboard_layout_belarusian"
+ android:label="@string/keyboard_layout_belarusian"
+ android:keyboardLayout="@raw/keyboard_layout_belarusian"
+ android:keyboardLocale="be-Cyrl" />
- <keyboard-layout android:name="keyboard_layout_georgian"
- android:label="@string/keyboard_layout_georgian"
- android:keyboardLayout="@raw/keyboard_layout_georgian" />
+ <keyboard-layout
+ android:name="keyboard_layout_mongolian"
+ android:label="@string/keyboard_layout_mongolian"
+ android:keyboardLayout="@raw/keyboard_layout_mongolian"
+ android:keyboardLocale="mn-Cyrl" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_georgian"
+ android:label="@string/keyboard_layout_georgian"
+ android:keyboardLayout="@raw/keyboard_layout_georgian"
+ android:keyboardLocale="ka-Geor" />
</keyboard-layouts>
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index fe640ad..fd982f5 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -39,12 +39,13 @@
certificate: "platform",
privileged: true,
- platform_apis: true,
+ platform_apis: false,
+ sdk_version: "system_current",
rename_resources_package: false,
-
static_libs: [
"xz-java",
"androidx.leanback_leanback",
+ "androidx.annotation_annotation",
],
}
@@ -56,7 +57,8 @@
certificate: "platform",
privileged: true,
- platform_apis: true,
+ platform_apis: false,
+ sdk_version: "system_current",
rename_resources_package: false,
overrides: ["PackageInstaller"],
@@ -75,13 +77,15 @@
certificate: "platform",
privileged: true,
- platform_apis: true,
+ platform_apis: false,
+ sdk_version: "system_current",
rename_resources_package: false,
overrides: ["PackageInstaller"],
static_libs: [
"xz-java",
"androidx.leanback_leanback",
+ "androidx.annotation_annotation",
],
aaptflags: ["--product tv"],
}
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png
new file mode 100644
index 0000000..6e5fbb5
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png
new file mode 100644
index 0000000..3434b2d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png
new file mode 100644
index 0000000..673a509
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png
new file mode 100644
index 0000000..c2a739c
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png
new file mode 100644
index 0000000..9d2bfb1
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png
new file mode 100644
index 0000000..4375bf2d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png
new file mode 100644
index 0000000..6b8aa9d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png
new file mode 100644
index 0000000..2884abe
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png
new file mode 100644
index 0000000..76c35ec
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png
new file mode 100644
index 0000000..f317330
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable/ic_dialog_info.png b/packages/PackageInstaller/res/drawable/ic_dialog_info.png
new file mode 100644
index 0000000..efee1ef
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable/ic_dialog_info.png
Binary files differ
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
new file mode 100644
index 0000000..3ced1db
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
@@ -0,0 +1,59 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:scrollIndicators="top|bottom"
+ android:fillViewport="true"
+ style="?android:attr/buttonBarStyle">
+ <com.android.packageinstaller.ButtonBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="locale"
+ android:orientation="horizontal"
+ android:gravity="start">
+
+ <Button
+ android:id="@+id/button1"
+ style="?android:attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:id="@+id/button2"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+
+ <Button
+ android:id="@+id/button3"
+ style="?attr/buttonBarNeutralButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </com.android.packageinstaller.ButtonBarLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml
new file mode 100644
index 0000000..0290624
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml
@@ -0,0 +1,84 @@
+<?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.
+-->
+
+<com.android.packageinstaller.AlertDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start|top"
+ android:orientation="vertical">
+
+ <include layout="@layout/alert_dialog_title_material" />
+
+ <FrameLayout
+ android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp">
+
+ <ScrollView
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <Space
+ android:id="@+id/textSpacerNoTitle"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialog_padding_top_material" />
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:paddingStart="?android:attr/dialogPreferredPadding"
+ style="@android:style/TextAppearance.Material.Subhead" />
+
+ <Space
+ android:id="@+id/textSpacerNoButtons"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialog_padding_top_material" />
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp">
+
+ <FrameLayout
+ android:id="@+id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/alert_dialog_button_bar_leanback" />
+</com.android.packageinstaller.AlertDialogLayout>
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml
new file mode 100644
index 0000000..f9668dd
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:id="@+id/leftPanel"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout android:id="@+id/topPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout android:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical|start"
+ android:paddingStart="16dip"
+ android:paddingEnd="16dip"
+ android:paddingTop="16dip"
+ android:paddingBottom="8dip">
+ <ImageView android:id="@+id/icon"
+ android:layout_width="32dip"
+ android:layout_height="32dip"
+ android:layout_marginEnd="8dip"
+ android:scaleType="fitCenter"
+ android:src="@null" />
+ <com.android.packageinstaller.DialogTitle android:id="@+id/alertTitle"
+ style="?android:attr/windowTitleStyle"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart" />
+ </LinearLayout>
+ <!-- If the client uses a customTitle, it will be added here. -->
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:minHeight="64dp">
+ <ScrollView android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+ <TextView android:id="@+id/message"
+ style="?android:attr/textAppearanceMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="16dip"
+ android:paddingEnd="16dip"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip" />
+ </ScrollView>
+ </LinearLayout>
+
+ <FrameLayout android:id="@+id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:minHeight="64dp">
+ <FrameLayout android:id="@+id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/buttonPanel"
+ style="?attr/buttonBarStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical"
+ android:gravity="end">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layoutDirection="locale"
+ android:orientation="vertical">
+ <Button android:id="@+id/button3"
+ style="?attr/buttonBarNeutralButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:minHeight="@dimen/alert_dialog_button_bar_height" />
+ <Button android:id="@+id/button2"
+ style="?attr/buttonBarNegativeButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:minHeight="@dimen/alert_dialog_button_bar_height" />
+ <Button android:id="@+id/button1"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:minHeight="@dimen/alert_dialog_button_bar_height" />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml
new file mode 100644
index 0000000..e4977e7
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:scrollIndicators="top|bottom"
+ android:fillViewport="true"
+ style="?android:attr/buttonBarStyle">
+ <com.android.packageinstaller.ButtonBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="locale"
+ android:orientation="horizontal"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:gravity="bottom">
+
+ <Button
+ android:id="@+id/button3"
+ style="?android:attr/buttonBarNeutralButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+
+ <Button
+ android:id="@+id/button2"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:id="@+id/button1"
+ style="?android:attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </com.android.packageinstaller.ButtonBarLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_material.xml
new file mode 100644
index 0000000..10e9149
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_material.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.packageinstaller.AlertDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start|top"
+ android:orientation="vertical">
+
+ <include layout="@layout/alert_dialog_title_material" />
+
+ <FrameLayout
+ android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp">
+
+ <ScrollView
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <Space
+ android:id="@+id/textSpacerNoTitle"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialog_padding_top_material" />
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:paddingStart="?android:attr/dialogPreferredPadding"
+ style="@android:style/TextAppearance.Material.Subhead" />
+
+ <Space
+ android:id="@+id/textSpacerNoButtons"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialog_padding_top_material" />
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp">
+
+ <FrameLayout
+ android:id="@+id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/alert_dialog_button_bar_material" />
+</com.android.packageinstaller.AlertDialogLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_progress.xml b/packages/PackageInstaller/res/layout/alert_dialog_progress.xml
new file mode 100644
index 0000000..fe06b65
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_progress.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content" android:layout_height="match_parent">
+ <ProgressBar android:id="@+id/progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dip"
+ android:layout_marginBottom="1dip"
+ android:layout_marginStart="10dip"
+ android:layout_marginEnd="10dip"
+ android:layout_centerHorizontal="true" />
+ <TextView
+ android:id="@+id/progress_percent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="12dip"
+ android:layout_marginStart="10dip"
+ android:layout_marginEnd="10dip"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/progress"
+ />
+ <TextView
+ android:id="@+id/progress_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="12dip"
+ android:layout_marginStart="10dip"
+ android:layout_marginEnd="10dip"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/progress"
+ />
+</RelativeLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml
new file mode 100644
index 0000000..fb98259
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingTop="@dimen/dialog_padding_top_material"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingBottom="@dimen/dialog_padding_top_material">
+ <ProgressBar
+ android:id="@+id/progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true" />
+ <TextView
+ android:id="@+id/progress_percent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/progress" />
+ <TextView
+ android:id="@+id/progress_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/progress" />
+</RelativeLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml
new file mode 100644
index 0000000..45d9bb6
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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/topPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- If the client uses a customTitle, it will be added here. -->
+
+ <LinearLayout
+ android:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical|start"
+ android:paddingStart="?android:attr/dialogPreferredPadding"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:paddingTop="@dimen/dialog_padding_top_material">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="32dip"
+ android:layout_height="32dip"
+ android:layout_marginEnd="8dip"
+ android:scaleType="fitCenter"
+ android:src="@null" />
+
+ <com.android.packageinstaller.DialogTitle
+ android:id="@+id/alertTitle"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ style="?android:attr/windowTitleStyle" />
+ </LinearLayout>
+
+ <Space
+ android:id="@+id/titleDividerNoCustom"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/dialog_title_divider_material" />
+</LinearLayout>
diff --git a/packages/PackageInstaller/res/layout/progress_dialog.xml b/packages/PackageInstaller/res/layout/progress_dialog.xml
new file mode 100644
index 0000000..0d3cd4a
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/progress_dialog.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout android:id="@+id/body"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:baselineAligned="false"
+ android:paddingStart="8dip"
+ android:paddingTop="10dip"
+ android:paddingEnd="8dip"
+ android:paddingBottom="10dip">
+
+ <ProgressBar android:id="@android:id/progress"
+ style="?android:attr/progressBarStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:max="10000"
+ android:layout_marginEnd="12dip" />
+
+ <TextView android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" />
+ </LinearLayout>
+</FrameLayout>
diff --git a/packages/PackageInstaller/res/layout/progress_dialog_material.xml b/packages/PackageInstaller/res/layout/progress_dialog_material.xml
new file mode 100644
index 0000000..2417965
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/progress_dialog_material.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/body"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:baselineAligned="false"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingTop="@dimen/dialog_padding_top_material"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingBottom="@dimen/dialog_padding_top_material">
+
+ <ProgressBar
+ android:id="@id/progress"
+ style="?android:attr/progressBarStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:max="10000"
+ android:layout_marginEnd="?attr/dialogPreferredPadding" />
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" />
+ </LinearLayout>
+</FrameLayout>
diff --git a/packages/PackageInstaller/res/layout/select_dialog_item_material.xml b/packages/PackageInstaller/res/layout/select_dialog_item_material.xml
new file mode 100644
index 0000000..b45edc6
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_item_material.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<!--
+ This layout file is used by the AlertDialog when displaying a list of items.
+ This layout file is inflated and used as the TextView to display individual
+ items.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:gravity="center_vertical"
+ android:paddingStart="?attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+ android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/layout/select_dialog_material.xml b/packages/PackageInstaller/res/layout/select_dialog_material.xml
new file mode 100644
index 0000000..125b9b8
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_material.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<!--
+ This layout file is used by the AlertDialog when displaying a list of items.
+ This layout file is inflated and used as the ListView to display the items.
+ Assign an ID so its state will be saved/restored.
+-->
+<view class="com.android.packageinstaller.AlertController$RecycleListView"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@id/select_dialog_listview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:cacheColorHint="@null"
+ android:divider="?attr/listDividerAlertDialog"
+ android:scrollbars="vertical"
+ android:overScrollMode="ifContentScrolls"
+ android:textAlignment="viewStart"
+ android:clipToPadding="false"/>
+ <!--android:paddingBottomNoButtons="@dimen/dialog_list_padding_bottom_no_buttons"
+ android:paddingTopNoTitle="@dimen/dialog_list_padding_top_no_title"/>-->
diff --git a/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml b/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml
new file mode 100644
index 0000000..52f709e
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?attr/textColorAlertDialogListItem"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/select_dialog_padding_start_material"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
+ android:drawablePadding="@dimen/select_dialog_drawable_padding_start_material"
+ android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml b/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml
new file mode 100644
index 0000000..8345b18
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?attr/textColorAlertDialogListItem"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/select_dialog_padding_start_material"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+ android:drawablePadding="@dimen/select_dialog_drawable_padding_start_material"
+ android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/values-night/themes.xml b/packages/PackageInstaller/res/values-night/themes.xml
index 483b0cf..18320f7 100644
--- a/packages/PackageInstaller/res/values-night/themes.xml
+++ b/packages/PackageInstaller/res/values-night/themes.xml
@@ -18,6 +18,8 @@
<resources>
<style name="Theme.AlertDialogActivity"
- parent="@android:style/Theme.DeviceDefault.Dialog.Alert" />
+ parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+ <item name="alertDialogStyle">@style/AlertDialog</item>
+ </style>
</resources>
diff --git a/packages/PackageInstaller/res/values-television/styles.xml b/packages/PackageInstaller/res/values-television/styles.xml
new file mode 100644
index 0000000..936fff0
--- /dev/null
+++ b/packages/PackageInstaller/res/values-television/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <style name="AlertDialog.Leanback" parent="@style/AlertDialog">
+ <item name="buttonPanelSideLayout">@layout/alert_dialog_leanback_button_panel_side</item>
+ <item name="layout">@layout/alert_dialog_leanback</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values-television/themes.xml b/packages/PackageInstaller/res/values-television/themes.xml
index 5ae4957..1cc6933 100644
--- a/packages/PackageInstaller/res/values-television/themes.xml
+++ b/packages/PackageInstaller/res/values-television/themes.xml
@@ -17,15 +17,20 @@
<resources>
- <style name="Theme.AlertDialogActivity.NoAnimation">
+ <style name="Theme.AlertDialogActivity.NoAnimation"
+ parent="@style/Theme.AlertDialogActivity.NoActionBar">
<item name="android:windowAnimationStyle">@null</item>
</style>
<style name="Theme.AlertDialogActivity"
- parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+ <item name="alertDialogStyle">@style/AlertDialog.Leanback</item>
+ </style>
<style name="Theme.AlertDialogActivity.NoActionBar"
- parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+ parent="@android:style/Theme.Material.Light.NoActionBar">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
diff --git a/packages/PackageInstaller/res/values/attrs.xml b/packages/PackageInstaller/res/values/attrs.xml
index e220f4c..e3070e2 100644
--- a/packages/PackageInstaller/res/values/attrs.xml
+++ b/packages/PackageInstaller/res/values/attrs.xml
@@ -32,4 +32,49 @@
<attr name="circle_radius_pressed_percent" format="fraction" />
</declare-styleable>
<!-- END: Ported from WearableSupport -->
+ <declare-styleable name="Theme">
+ <attr name="alertDialogCenterButtons" format="boolean" />
+ </declare-styleable>
+ <declare-styleable name="AlertDialog">
+ <attr name="fullDark" format="reference|color" />
+ <attr name="topDark" format="reference|color" />
+ <attr name="centerDark" format="reference|color" />
+ <attr name="bottomDark" format="reference|color" />
+ <attr name="fullBright" format="reference|color" />
+ <attr name="topBright" format="reference|color" />
+ <attr name="centerBright" format="reference|color" />
+ <attr name="bottomBright" format="reference|color" />
+ <attr name="bottomMedium" format="reference|color" />
+ <attr name="centerMedium" format="reference|color" />
+ <attr name="layout" format="reference" />
+ <attr name="buttonPanelSideLayout" format="reference" />
+ <attr name="listLayout" format="reference" />
+ <attr name="multiChoiceItemLayout" format="reference" />
+ <attr name="singleChoiceItemLayout" format="reference" />
+ <attr name="listItemLayout" format="reference" />
+ <attr name="progressLayout" format="reference" />
+ <attr name="horizontalProgressLayout" format="reference" />
+ <!-- @hide Not ready for public use. -->
+ <attr name="showTitle" format="boolean" />
+ <!-- Whether fullDark, etc. should use default values if null. -->
+ <attr name="needsDefaultBackgrounds" format="boolean" />
+ <!-- Workaround until we replace AlertController with custom layout. -->
+ <attr name="controllerType">
+ <!-- The default controller. -->
+ <enum name="normal" value="0" />
+ <!-- Controller for micro specific layout. -->
+ <enum name="micro" value="1" />
+ </attr>
+ <!-- Offset when scrolling to a selection. -->
+ <attr name="selectionScrollOffset" format="dimension" />
+ </declare-styleable>
+ <declare-styleable name="ButtonBarLayout">
+ <!-- Whether to automatically stack the buttons when there is not
+ enough space to lay them out side-by-side. -->
+ <attr name="allowStacking" format="boolean" />
+ </declare-styleable>
+ <declare-styleable name="TextAppearance">
+ <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
+ <attr name="textSize" format="dimension" />
+ </declare-styleable>
</resources>
diff --git a/packages/PackageInstaller/res/values/dimens.xml b/packages/PackageInstaller/res/values/dimens.xml
index 112723f..bfea05e 100644
--- a/packages/PackageInstaller/res/values/dimens.xml
+++ b/packages/PackageInstaller/res/values/dimens.xml
@@ -41,4 +41,16 @@
<dimen name="wear_permission_review_pref_padding">8dp</dimen>
<dimen name="wear_permission_review_icon_size">24dp</dimen>
+
+ <!-- Dialog title height -->
+ <dimen name="alert_dialog_title_height">64dip</dimen>
+ <!-- Dialog button bar height -->
+ <dimen name="alert_dialog_button_bar_height">48dip</dimen>
+ <!-- The amount to offset when scrolling to a selection in an AlertDialog -->
+ <dimen name="config_alertDialogSelectionScrollOffset">0dp</dimen>
+ <dimen name="dialog_padding_top_material">18dp</dimen>
+ <dimen name="dialog_title_divider_material">8dp</dimen>
+ <!-- Dialog padding minus control padding, used to fix alignment. -->
+ <dimen name="select_dialog_padding_start_material">20dp</dimen>
+ <dimen name="select_dialog_drawable_padding_start_material">20dp</dimen>
</resources>
diff --git a/packages/PackageInstaller/res/values/integers.xml b/packages/PackageInstaller/res/values/integers.xml
new file mode 100644
index 0000000..22ad3a3
--- /dev/null
+++ b/packages/PackageInstaller/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <!-- The alert controller to use for alert dialogs. -->
+ <integer name="config_alertDialogController">0</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/styles.xml b/packages/PackageInstaller/res/values/styles.xml
new file mode 100644
index 0000000..ca797e1
--- /dev/null
+++ b/packages/PackageInstaller/res/values/styles.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <style name="AlertDialog">
+ <item name="fullDark">@empty</item>
+ <item name="topDark">@empty</item>
+ <item name="centerDark">@empty</item>
+ <item name="bottomDark">@empty</item>
+ <item name="fullBright">@empty</item>
+ <item name="topBright">@empty</item>
+ <item name="centerBright">@empty</item>
+ <item name="bottomBright">@empty</item>
+ <item name="bottomMedium">@empty</item>
+ <item name="centerMedium">@empty</item>
+ <item name="layout">@layout/alert_dialog_material</item>
+ <item name="listLayout">@layout/select_dialog_material</item>
+ <item name="progressLayout">@layout/progress_dialog_material</item>
+ <item name="horizontalProgressLayout">@layout/alert_dialog_progress_material</item>
+ <item name="listItemLayout">@layout/select_dialog_item_material</item>
+ <item name="multiChoiceItemLayout">@layout/select_dialog_multichoice_material</item>
+ <item name="singleChoiceItemLayout">@layout/select_dialog_singlechoice_material</item>
+ <item name="controllerType">@integer/config_alertDialogController</item>
+ <item name="selectionScrollOffset">@dimen/config_alertDialogSelectionScrollOffset</item>
+ <item name="needsDefaultBackgrounds">false</item>
+ </style>
+ <style name="TextAppearance">
+ <item name="textSize">16sp</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index eecf9a1..9a06229 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -23,7 +23,9 @@
</style>
<style name="Theme.AlertDialogActivity"
- parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+ <item name="alertDialogStyle">@style/AlertDialog</item>
+ </style>
<style name="Theme.AlertDialogActivity.NoActionBar">
<item name="android:windowActionBar">false</item>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java
new file mode 100644
index 0000000..7947400
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 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.packageinstaller;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * An activity that follows the visual style of an AlertDialog.
+ *
+ * @see #mAlert
+ * @see #setupAlert()
+ */
+public abstract class AlertActivity extends Activity implements DialogInterface {
+
+ public AlertActivity() {
+ }
+
+ /**
+ * The model for the alert.
+ *
+ */
+ protected AlertController mAlert;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mAlert = new AlertController(this, this, getWindow());
+ }
+
+ public void cancel() {
+ finish();
+ }
+
+ public void dismiss() {
+ // This is called after the click, since we finish when handling the
+ // click, don't do that again here.
+ if (!isFinishing()) {
+ finish();
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return dispatchPopulateAccessibilityEvent(this, event);
+ }
+
+ public static boolean dispatchPopulateAccessibilityEvent(Activity act,
+ AccessibilityEvent event) {
+ event.setClassName(Dialog.class.getName());
+ event.setPackageName(act.getPackageName());
+
+ ViewGroup.LayoutParams params = act.getWindow().getAttributes();
+ boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
+ (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
+ event.setFullScreen(isFullScreen);
+
+ return false;
+ }
+
+ /**
+ * Sets up the alert, including applying the parameters to the alert model,
+ * and installing the alert's content.
+ *
+ * @see #mAlert
+ */
+ protected void setupAlert() {
+ mAlert.installContent();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyDown(keyCode, event)) return true;
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyUp(keyCode, event)) return true;
+ return super.onKeyUp(keyCode, event);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java
new file mode 100644
index 0000000..33f38a6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2008 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.packageinstaller;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
+import android.view.ViewStub;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.packageinstaller.R;
+
+import java.lang.ref.WeakReference;
+
+public class AlertController {
+ private final Context mContext;
+ private final DialogInterface mDialogInterface;
+ protected final Window mWindow;
+
+ private CharSequence mTitle;
+ protected CharSequence mMessage;
+ protected ListView mListView;
+ private View mView;
+
+ private int mViewLayoutResId;
+
+ private int mViewSpacingLeft;
+ private int mViewSpacingTop;
+ private int mViewSpacingRight;
+ private int mViewSpacingBottom;
+ private boolean mViewSpacingSpecified = false;
+
+ private Button mButtonPositive;
+ private CharSequence mButtonPositiveText;
+ private Message mButtonPositiveMessage;
+
+ private Button mButtonNegative;
+ private CharSequence mButtonNegativeText;
+ private Message mButtonNegativeMessage;
+
+ private Button mButtonNeutral;
+ private CharSequence mButtonNeutralText;
+ private Message mButtonNeutralMessage;
+
+ protected ScrollView mScrollView;
+
+ private int mIconId = 0;
+ private Drawable mIcon;
+
+ private ImageView mIconView;
+ private TextView mTitleView;
+ protected TextView mMessageView;
+
+ private ListAdapter mAdapter;
+
+ private int mAlertDialogLayout;
+ private int mButtonPanelSideLayout;
+ private int mListLayout;
+ private int mMultiChoiceItemLayout;
+ private int mSingleChoiceItemLayout;
+ private int mListItemLayout;
+
+ private boolean mShowTitle;
+
+ private Handler mHandler;
+
+ private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Message m;
+ if (v == mButtonPositive && mButtonPositiveMessage != null) {
+ m = Message.obtain(mButtonPositiveMessage);
+ } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
+ m = Message.obtain(mButtonNegativeMessage);
+ } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
+ m = Message.obtain(mButtonNeutralMessage);
+ } else {
+ m = null;
+ }
+
+ if (m != null) {
+ m.sendToTarget();
+ }
+
+ // Post a message so we dismiss after the above handlers are executed
+ mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
+ .sendToTarget();
+ }
+ };
+
+ private static final class ButtonHandler extends Handler {
+ // Button clicks have Message.what as the BUTTON{1,2,3} constant
+ private static final int MSG_DISMISS_DIALOG = 1;
+
+ private WeakReference<DialogInterface> mDialog;
+
+ public ButtonHandler(DialogInterface dialog) {
+ mDialog = new WeakReference<>(dialog);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+
+ case DialogInterface.BUTTON_POSITIVE:
+ case DialogInterface.BUTTON_NEGATIVE:
+ case DialogInterface.BUTTON_NEUTRAL:
+ ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
+ break;
+
+ case MSG_DISMISS_DIALOG:
+ ((DialogInterface) msg.obj).dismiss();
+ }
+ }
+ }
+
+ public AlertController(Context context, DialogInterface di, Window window) {
+ mContext = context;
+ mDialogInterface = di;
+ mWindow = window;
+ mHandler = new ButtonHandler(di);
+
+ final TypedArray a = context.obtainStyledAttributes(null,
+ R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+
+ mAlertDialogLayout = a.getResourceId(
+ R.styleable.AlertDialog_layout, R.layout.alert_dialog_material);
+ mButtonPanelSideLayout = a.getResourceId(
+ R.styleable.AlertDialog_buttonPanelSideLayout, 0);
+ mListLayout = a.getResourceId(
+ R.styleable.AlertDialog_listLayout, R.layout.select_dialog_material);
+
+ mMultiChoiceItemLayout = a.getResourceId(
+ R.styleable.AlertDialog_multiChoiceItemLayout,
+ R.layout.select_dialog_multichoice_material);
+ mSingleChoiceItemLayout = a.getResourceId(
+ R.styleable.AlertDialog_singleChoiceItemLayout,
+ R.layout.select_dialog_singlechoice_material);
+ mListItemLayout = a.getResourceId(
+ R.styleable.AlertDialog_listItemLayout,
+ R.layout.select_dialog_item_material);
+ mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
+
+ a.recycle();
+
+ /* We use a custom title so never request a window title */
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ }
+
+ static boolean canTextInput(View v) {
+ if (v.onCheckIsTextEditor()) {
+ return true;
+ }
+
+ if (!(v instanceof ViewGroup)) {
+ return false;
+ }
+
+ ViewGroup vg = (ViewGroup)v;
+ int i = vg.getChildCount();
+ while (i > 0) {
+ i--;
+ v = vg.getChildAt(i);
+ if (canTextInput(v)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void installContent() {
+ mWindow.setContentView(mAlertDialogLayout);
+ setupView();
+ }
+
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ if (mTitleView != null) {
+ mTitleView.setText(title);
+ }
+ mWindow.setTitle(title);
+ }
+
+ /**
+ * Set the view resource to display in the dialog.
+ */
+ public void setView(int layoutResId) {
+ mView = null;
+ mViewLayoutResId = layoutResId;
+ mViewSpacingSpecified = false;
+ }
+
+ /**
+ * Sets a click listener or a message to be sent when the button is clicked.
+ * You only need to pass one of {@code listener} or {@code msg}.
+ *
+ * @param whichButton Which button, can be one of
+ * {@link DialogInterface#BUTTON_POSITIVE},
+ * {@link DialogInterface#BUTTON_NEGATIVE}, or
+ * {@link DialogInterface#BUTTON_NEUTRAL}
+ * @param text The text to display in positive button.
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @param msg The {@link Message} to be sent when clicked.
+ */
+ public void setButton(int whichButton, CharSequence text,
+ DialogInterface.OnClickListener listener, Message msg) {
+
+ if (msg == null && listener != null) {
+ msg = mHandler.obtainMessage(whichButton, listener);
+ }
+
+ switch (whichButton) {
+
+ case DialogInterface.BUTTON_POSITIVE:
+ mButtonPositiveText = text;
+ mButtonPositiveMessage = msg;
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ mButtonNegativeText = text;
+ mButtonNegativeMessage = msg;
+ break;
+
+ case DialogInterface.BUTTON_NEUTRAL:
+ mButtonNeutralText = text;
+ mButtonNeutralMessage = msg;
+ break;
+
+ default:
+ throw new IllegalArgumentException("Button does not exist");
+ }
+ }
+
+ /**
+ * Specifies the icon to display next to the alert title.
+ *
+ * @param resId the resource identifier of the drawable to use as the icon,
+ * or 0 for no icon
+ */
+ public void setIcon(int resId) {
+ mIcon = null;
+ mIconId = resId;
+
+ if (mIconView != null) {
+ if (resId != 0) {
+ mIconView.setVisibility(View.VISIBLE);
+ mIconView.setImageResource(mIconId);
+ } else {
+ mIconView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ /**
+ * Specifies the icon to display next to the alert title.
+ *
+ * @param icon the drawable to use as the icon or null for no icon
+ */
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ mIconId = 0;
+
+ if (mIconView != null) {
+ if (icon != null) {
+ mIconView.setVisibility(View.VISIBLE);
+ mIconView.setImageDrawable(icon);
+ } else {
+ mIconView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ public Button getButton(int whichButton) {
+ switch (whichButton) {
+ case DialogInterface.BUTTON_POSITIVE:
+ return mButtonPositive;
+ case DialogInterface.BUTTON_NEGATIVE:
+ return mButtonNegative;
+ case DialogInterface.BUTTON_NEUTRAL:
+ return mButtonNeutral;
+ default:
+ return null;
+ }
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
+ }
+
+ /**
+ * Resolves whether a custom or default panel should be used. Removes the
+ * default panel if a custom panel should be used. If the resolved panel is
+ * a view stub, inflates before returning.
+ *
+ * @param customPanel the custom panel
+ * @param defaultPanel the default panel
+ * @return the panel to use
+ */
+ @Nullable
+ private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
+ if (customPanel == null) {
+ // Inflate the default panel, if needed.
+ if (defaultPanel instanceof ViewStub) {
+ defaultPanel = ((ViewStub) defaultPanel).inflate();
+ }
+
+ return (ViewGroup) defaultPanel;
+ }
+
+ // Remove the default panel entirely.
+ if (defaultPanel != null) {
+ final ViewParent parent = defaultPanel.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(defaultPanel);
+ }
+ }
+
+ // Inflate the custom panel, if needed.
+ if (customPanel instanceof ViewStub) {
+ customPanel = ((ViewStub) customPanel).inflate();
+ }
+
+ return (ViewGroup) customPanel;
+ }
+
+ private void setupView() {
+ final View parentPanel = mWindow.findViewById(R.id.parentPanel);
+ final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
+ final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
+ final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
+
+ // Install custom content before setting up the title or buttons so
+ // that we can handle panel overrides.
+ final ViewGroup customPanel = parentPanel.findViewById(R.id.customPanel);
+ setupCustomContent(customPanel);
+
+ final View customTopPanel = customPanel.findViewById(R.id.topPanel);
+ final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
+ final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
+
+ // Resolve the correct panels and remove the defaults, if needed.
+ final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
+ final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
+ final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
+
+ setupContent(contentPanel);
+ setupButtons(buttonPanel);
+ setupTitle(topPanel);
+
+ final boolean hasCustomPanel = customPanel != null
+ && customPanel.getVisibility() != View.GONE;
+ final boolean hasTopPanel = topPanel != null
+ && topPanel.getVisibility() != View.GONE;
+ final boolean hasButtonPanel = buttonPanel != null
+ && buttonPanel.getVisibility() != View.GONE;
+
+ if (!parentPanel.isInTouchMode()) {
+ final View content = hasCustomPanel ? customPanel : contentPanel;
+ if (!requestFocusForContent(content)) {
+ requestFocusForDefaultButton();
+ }
+ }
+
+ if (hasTopPanel) {
+ // Only clip scrolling content to padding if we have a title.
+ if (mScrollView != null) {
+ mScrollView.setClipToPadding(true);
+ }
+
+ // Only show the divider if we have a title.
+ View divider = null;
+ if (mMessage != null || hasCustomPanel) {
+ divider = topPanel.findViewById(R.id.titleDividerNoCustom);
+ }
+
+ if (divider != null) {
+ divider.setVisibility(View.VISIBLE);
+ }
+ } else {
+ if (contentPanel != null) {
+ final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
+ if (spacer != null) {
+ spacer.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ // Update scroll indicators as needed.
+ if (!hasCustomPanel) {
+ final View content = mScrollView;
+ if (content != null) {
+ final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
+ | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
+ content.setScrollIndicators(indicators,
+ View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
+ }
+ }
+
+ final TypedArray a = mContext.obtainStyledAttributes(
+ null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+ setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
+ hasTopPanel, hasCustomPanel, hasButtonPanel);
+ a.recycle();
+ }
+
+ private boolean requestFocusForContent(View content) {
+ return content != null && content.requestFocus();
+ }
+
+ private void requestFocusForDefaultButton() {
+ if (mButtonPositive.getVisibility() == View.VISIBLE) {
+ mButtonPositive.requestFocus();
+ } else if (mButtonNegative.getVisibility() == View.VISIBLE) {
+ mButtonNegative.requestFocus();
+ } else if (mButtonNeutral.getVisibility() == View.VISIBLE) {
+ mButtonNeutral.requestFocus();
+ }
+ }
+
+ private void setupCustomContent(ViewGroup customPanel) {
+ final View customView;
+ if (mView != null) {
+ customView = mView;
+ } else if (mViewLayoutResId != 0) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ customView = inflater.inflate(mViewLayoutResId, customPanel, false);
+ } else {
+ customView = null;
+ }
+
+ final boolean hasCustomView = customView != null;
+ if (!hasCustomView || !canTextInput(customView)) {
+ mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ }
+
+ if (hasCustomView) {
+ final FrameLayout custom = mWindow.findViewById(R.id.custom);
+ custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ if (mViewSpacingSpecified) {
+ custom.setPadding(
+ mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
+ }
+ } else {
+ customPanel.setVisibility(View.GONE);
+ }
+ }
+
+ private void setupTitle(ViewGroup topPanel) {
+ mIconView = mWindow.findViewById(R.id.icon);
+
+ final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
+ if (hasTextTitle && mShowTitle) {
+ // Display the title if a title is supplied, else hide it.
+ mTitleView = mWindow.findViewById(R.id.alertTitle);
+ mTitleView.setText(mTitle);
+
+ // Do this last so that if the user has supplied any icons we
+ // use them instead of the default ones. If the user has
+ // specified 0 then make it disappear.
+ if (mIconId != 0) {
+ mIconView.setImageResource(mIconId);
+ } else if (mIcon != null) {
+ mIconView.setImageDrawable(mIcon);
+ } else {
+ // Apply the padding from the icon to ensure the title is
+ // aligned correctly.
+ mTitleView.setPadding(mIconView.getPaddingLeft(),
+ mIconView.getPaddingTop(),
+ mIconView.getPaddingRight(),
+ mIconView.getPaddingBottom());
+ mIconView.setVisibility(View.GONE);
+ }
+ } else {
+ // Hide the title template
+ final View titleTemplate = mWindow.findViewById(R.id.title_template);
+ titleTemplate.setVisibility(View.GONE);
+ mIconView.setVisibility(View.GONE);
+ topPanel.setVisibility(View.GONE);
+ }
+ }
+
+ private void setupContent(ViewGroup contentPanel) {
+ mScrollView = contentPanel.findViewById(R.id.scrollView);
+ mScrollView.setFocusable(false);
+
+ // Special case for users that only want to display a String
+ mMessageView = contentPanel.findViewById(R.id.message);
+ if (mMessageView == null) {
+ return;
+ }
+
+ mMessageView.setVisibility(View.GONE);
+ mScrollView.removeView(mMessageView);
+
+ contentPanel.setVisibility(View.GONE);
+ }
+
+ private void setupButtons(ViewGroup buttonPanel) {
+ int BIT_BUTTON_POSITIVE = 1;
+ int BIT_BUTTON_NEGATIVE = 2;
+ int BIT_BUTTON_NEUTRAL = 4;
+ int whichButtons = 0;
+ mButtonPositive = buttonPanel.findViewById(R.id.button1);
+ mButtonPositive.setOnClickListener(mButtonHandler);
+
+ if (TextUtils.isEmpty(mButtonPositiveText)) {
+ mButtonPositive.setVisibility(View.GONE);
+ } else {
+ mButtonPositive.setText(mButtonPositiveText);
+ mButtonPositive.setVisibility(View.VISIBLE);
+ whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
+ }
+
+ mButtonNegative = buttonPanel.findViewById(R.id.button2);
+ mButtonNegative.setOnClickListener(mButtonHandler);
+
+ if (TextUtils.isEmpty(mButtonNegativeText)) {
+ mButtonNegative.setVisibility(View.GONE);
+ } else {
+ mButtonNegative.setText(mButtonNegativeText);
+ mButtonNegative.setVisibility(View.VISIBLE);
+
+ whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
+ }
+
+ mButtonNeutral = buttonPanel.findViewById(R.id.button3);
+ mButtonNeutral.setOnClickListener(mButtonHandler);
+
+ if (TextUtils.isEmpty(mButtonNeutralText)) {
+ mButtonNeutral.setVisibility(View.GONE);
+ } else {
+ mButtonNeutral.setText(mButtonNeutralText);
+ mButtonNeutral.setVisibility(View.VISIBLE);
+
+ whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
+ }
+
+ final boolean hasButtons = whichButtons != 0;
+ if (!hasButtons) {
+ buttonPanel.setVisibility(View.GONE);
+ }
+ }
+
+ private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
+ View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
+ int fullDark = 0;
+ int topDark = 0;
+ int centerDark = 0;
+ int bottomDark = 0;
+ int fullBright = 0;
+ int topBright = 0;
+ int centerBright = 0;
+ int bottomBright = 0;
+ int bottomMedium = 0;
+
+ topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright);
+ topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark);
+ centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright);
+ centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark);
+
+ /* We now set the background of all of the sections of the alert.
+ * First collect together each section that is being displayed along
+ * with whether it is on a light or dark background, then run through
+ * them setting their backgrounds. This is complicated because we need
+ * to correctly use the full, top, middle, and bottom graphics depending
+ * on how many views they are and where they appear.
+ */
+
+ final View[] views = new View[4];
+ final boolean[] light = new boolean[4];
+ View lastView = null;
+ boolean lastLight = false;
+
+ int pos = 0;
+ if (hasTitle) {
+ views[pos] = topPanel;
+ light[pos] = false;
+ pos++;
+ }
+
+ /* The contentPanel displays either a custom text message or
+ * a ListView. If it's text we should use the dark background
+ * for ListView we should use the light background. PIA does not use
+ * a list view. Hence, we set it to use dark background. If neither
+ * are there the contentPanel will be hidden so set it as null.
+ */
+ views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
+ light[pos] = false;
+ pos++;
+
+ if (hasCustomView) {
+ views[pos] = customPanel;
+ light[pos] = false;
+ pos++;
+ }
+
+ if (hasButtons) {
+ views[pos] = buttonPanel;
+ light[pos] = true;
+ }
+
+ boolean setView = false;
+ for (pos = 0; pos < views.length; pos++) {
+ final View v = views[pos];
+ if (v == null) {
+ continue;
+ }
+
+ if (lastView != null) {
+ if (!setView) {
+ lastView.setBackgroundResource(lastLight ? topBright : topDark);
+ } else {
+ lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
+ }
+ setView = true;
+ }
+
+ lastView = v;
+ lastLight = light[pos];
+ }
+
+ if (lastView != null) {
+ if (setView) {
+ bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright);
+ bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium);
+ bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark);
+
+ // ListViews will use the Bright background, but buttons use the
+ // Medium background.
+ lastView.setBackgroundResource(
+ lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
+ } else {
+ fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright);
+ fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark);
+
+ lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
+ }
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java
new file mode 100644
index 0000000..e22171e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+
+import com.android.packageinstaller.R;
+
+/**
+ * Special implementation of linear layout that's capable of laying out alert
+ * dialog components.
+ * <p>
+ * A dialog consists of up to three panels. All panels are optional, and a
+ * dialog may contain only a single panel. The panels are laid out according
+ * to the following guidelines:
+ * <ul>
+ * <li>topPanel: exactly wrap_content</li>
+ * <li>contentPanel OR customPanel: at most fill_parent, first priority for
+ * extra space</li>
+ * <li>buttonPanel: at least minHeight, at most wrap_content, second
+ * priority for extra space</li>
+ * </ul>
+ */
+public class AlertDialogLayout extends LinearLayout {
+
+ public AlertDialogLayout(@Nullable Context context) {
+ super(context);
+ }
+
+ public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!tryOnMeasure(widthMeasureSpec, heightMeasureSpec)) {
+ // Failed to perform custom measurement, let superclass handle it.
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private boolean tryOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ View topPanel = null;
+ View buttonPanel = null;
+ View middlePanel = null;
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final int id = child.getId();
+ switch (id) {
+ case R.id.topPanel:
+ topPanel = child;
+ break;
+ case R.id.buttonPanel:
+ buttonPanel = child;
+ break;
+ case R.id.contentPanel:
+ case R.id.customPanel:
+ if (middlePanel != null) {
+ // Both the content and custom are visible. Abort!
+ return false;
+ }
+ middlePanel = child;
+ break;
+ default:
+ // Unknown top-level child. Abort!
+ return false;
+ }
+ }
+
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+ int childState = 0;
+ int usedHeight = getPaddingTop() + getPaddingBottom();
+
+ if (topPanel != null) {
+ topPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+
+ usedHeight += topPanel.getMeasuredHeight();
+ childState = combineMeasuredStates(childState, topPanel.getMeasuredState());
+ }
+
+ int buttonHeight = 0;
+ int buttonWantsHeight = 0;
+ if (buttonPanel != null) {
+ buttonPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+ buttonHeight = resolveMinimumHeight(buttonPanel);
+ buttonWantsHeight = buttonPanel.getMeasuredHeight() - buttonHeight;
+
+ usedHeight += buttonHeight;
+ childState = combineMeasuredStates(childState, buttonPanel.getMeasuredState());
+ }
+
+ int middleHeight = 0;
+ if (middlePanel != null) {
+ final int childHeightSpec;
+ if (heightMode == MeasureSpec.UNSPECIFIED) {
+ childHeightSpec = MeasureSpec.UNSPECIFIED;
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(
+ Math.max(0, heightSize - usedHeight), heightMode);
+ }
+
+ middlePanel.measure(widthMeasureSpec, childHeightSpec);
+ middleHeight = middlePanel.getMeasuredHeight();
+
+ usedHeight += middleHeight;
+ childState = combineMeasuredStates(childState, middlePanel.getMeasuredState());
+ }
+
+ int remainingHeight = heightSize - usedHeight;
+
+ // Time for the "real" button measure pass. If we have remaining space,
+ // make the button pane bigger up to its target height. Otherwise,
+ // just remeasure the button at whatever height it needs.
+ if (buttonPanel != null) {
+ usedHeight -= buttonHeight;
+
+ final int heightToGive = Math.min(remainingHeight, buttonWantsHeight);
+ if (heightToGive > 0) {
+ remainingHeight -= heightToGive;
+ buttonHeight += heightToGive;
+ }
+
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+ buttonHeight, MeasureSpec.EXACTLY);
+ buttonPanel.measure(widthMeasureSpec, childHeightSpec);
+
+ usedHeight += buttonPanel.getMeasuredHeight();
+ childState = combineMeasuredStates(childState, buttonPanel.getMeasuredState());
+ }
+
+ // If we still have remaining space, make the middle pane bigger up
+ // to the maximum height.
+ if (middlePanel != null && remainingHeight > 0) {
+ usedHeight -= middleHeight;
+
+ final int heightToGive = remainingHeight;
+ remainingHeight -= heightToGive;
+ middleHeight += heightToGive;
+
+ // Pass the same height mode as we're using for the dialog itself.
+ // If it's EXACTLY, then the middle pane MUST use the entire
+ // height.
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+ middleHeight, heightMode);
+ middlePanel.measure(widthMeasureSpec, childHeightSpec);
+
+ usedHeight += middlePanel.getMeasuredHeight();
+ childState = combineMeasuredStates(childState, middlePanel.getMeasuredState());
+ }
+
+ // Compute desired width as maximum child width.
+ int maxWidth = 0;
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE) {
+ maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+ }
+ }
+
+ maxWidth += getPaddingLeft() + getPaddingRight();
+
+ final int widthSizeAndState = resolveSizeAndState(maxWidth, widthMeasureSpec, childState);
+ final int heightSizeAndState = resolveSizeAndState(usedHeight, heightMeasureSpec, 0);
+ setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+
+ // If the children weren't already measured EXACTLY, we need to run
+ // another measure pass to for MATCH_PARENT widths.
+ if (widthMode != MeasureSpec.EXACTLY) {
+ forceUniformWidth(count, heightMeasureSpec);
+ }
+
+ return true;
+ }
+
+ /**
+ * Remeasures child views to exactly match the layout's measured width.
+ *
+ * @param count the number of child views
+ * @param heightMeasureSpec the original height measure spec
+ */
+ private void forceUniformWidth(int count, int heightMeasureSpec) {
+ // Pretend that the linear layout has an exact size.
+ final int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(
+ getMeasuredWidth(), MeasureSpec.EXACTLY);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.width == LayoutParams.MATCH_PARENT) {
+ // Temporarily force children to reuse their old measured
+ // height.
+ final int oldHeight = lp.height;
+ lp.height = child.getMeasuredHeight();
+
+ // Remeasure with new dimensions.
+ measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+ lp.height = oldHeight;
+ }
+ }
+ }
+ }
+
+ /**
+ * Attempts to resolve the minimum height of a view.
+ * <p>
+ * If the view doesn't have a minimum height set and only contains a single
+ * child, attempts to resolve the minimum height of the child view.
+ *
+ * @param v the view whose minimum height to resolve
+ * @return the minimum height
+ */
+ private int resolveMinimumHeight(View v) {
+ final int minHeight = v.getMinimumHeight();
+ if (minHeight > 0) {
+ return minHeight;
+ }
+
+ if (v instanceof ViewGroup) {
+ final ViewGroup vg = (ViewGroup) v;
+ if (vg.getChildCount() == 1) {
+ return resolveMinimumHeight(vg.getChildAt(0));
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int paddingLeft = getPaddingLeft();
+ final int paddingRight = getPaddingRight();
+ final int paddingTop = getPaddingTop();
+
+ // Where right end of child should go
+ final int width = right - left;
+ final int childRight = width - paddingRight;
+
+ // Space available for child
+ final int childSpace = width - paddingLeft - paddingRight;
+
+ final int totalLength = getMeasuredHeight();
+ final int count = getChildCount();
+ final int gravity = getGravity();
+ final int majorGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ final int minorGravity = gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+ int childTop;
+ switch (majorGravity) {
+ case Gravity.BOTTOM:
+ // totalLength contains the padding already
+ childTop = paddingTop + bottom - top - totalLength;
+ break;
+
+ // totalLength contains the padding already
+ case Gravity.CENTER_VERTICAL:
+ childTop = paddingTop + (bottom - top - totalLength) / 2;
+ break;
+
+ case Gravity.TOP:
+ default:
+ childTop = paddingTop;
+ break;
+ }
+
+ final Drawable dividerDrawable = getDividerDrawable();
+ final int dividerHeight = dividerDrawable == null ?
+ 0 : dividerDrawable.getIntrinsicHeight();
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child != null && child.getVisibility() != GONE) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ final LayoutParams lp =
+ (LayoutParams) child.getLayoutParams();
+
+ int layoutGravity = lp.gravity;
+ if (layoutGravity < 0) {
+ layoutGravity = minorGravity;
+ }
+ final int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(
+ layoutGravity, layoutDirection);
+
+ final int childLeft;
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ + lp.leftMargin - lp.rightMargin;
+ break;
+
+ case Gravity.RIGHT:
+ childLeft = childRight - childWidth - lp.rightMargin;
+ break;
+
+ case Gravity.LEFT:
+ default:
+ childLeft = paddingLeft + lp.leftMargin;
+ break;
+ }
+
+ childTop += lp.topMargin;
+ setChildFrame(child, childLeft, childTop, childWidth, childHeight);
+ childTop += childHeight + lp.bottomMargin;
+ }
+ }
+ }
+
+ private void setChildFrame(View child, int left, int top, int width, int height) {
+ child.layout(left, top, left + width, top + height);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java b/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java
new file mode 100644
index 0000000..8d478c4
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.packageinstaller.R;
+
+/**
+ * An extension of LinearLayout that automatically switches to vertical
+ * orientation when it can't fit its child views horizontally.
+ */
+public class ButtonBarLayout extends LinearLayout {
+ /** Amount of the second button to "peek" above the fold when stacked. */
+ private static final int PEEK_BUTTON_DP = 16;
+
+ /** Whether the current configuration allows stacking. */
+ private boolean mAllowStacking;
+
+ private int mLastWidthSize = -1;
+
+ private int mMinimumHeight = 0;
+
+ public ButtonBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
+ ta.recycle();
+ }
+
+ public void setAllowStacking(boolean allowStacking) {
+ if (mAllowStacking != allowStacking) {
+ mAllowStacking = allowStacking;
+ if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
+ setStacked(false);
+ }
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ if (mAllowStacking) {
+ if (widthSize > mLastWidthSize && isStacked()) {
+ // We're being measured wider this time, try un-stacking.
+ setStacked(false);
+ }
+
+ mLastWidthSize = widthSize;
+ }
+
+ boolean needsRemeasure = false;
+
+ // If we're not stacked, make sure the measure spec is AT_MOST rather
+ // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
+ // know to stack the buttons.
+ final int initialWidthMeasureSpec;
+ if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
+ initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+
+ // We'll need to remeasure again to fill excess space.
+ needsRemeasure = true;
+ } else {
+ initialWidthMeasureSpec = widthMeasureSpec;
+ }
+
+ super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
+
+ if (mAllowStacking && !isStacked()) {
+ final int measuredWidth = getMeasuredWidthAndState();
+ final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
+ if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
+ setStacked(true);
+
+ // Measure again in the new orientation.
+ needsRemeasure = true;
+ }
+ }
+
+ if (needsRemeasure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ // Compute minimum height such that, when stacked, some portion of the
+ // second button is visible.
+ int minHeight = 0;
+ final int firstVisible = getNextVisibleChildIndex(0);
+ if (firstVisible >= 0) {
+ final View firstButton = getChildAt(firstVisible);
+ final LayoutParams firstParams = (LayoutParams) firstButton.getLayoutParams();
+ minHeight += getPaddingTop() + firstButton.getMeasuredHeight()
+ + firstParams.topMargin + firstParams.bottomMargin;
+ if (isStacked()) {
+ final int secondVisible = getNextVisibleChildIndex(firstVisible + 1);
+ if (secondVisible >= 0) {
+ minHeight += getChildAt(secondVisible).getPaddingTop()
+ + PEEK_BUTTON_DP * getResources().getDisplayMetrics().density;
+ }
+ } else {
+ minHeight += getPaddingBottom();
+ }
+ }
+
+ if (getMinimumHeight() != minHeight) {
+ setMinimumHeight(minHeight);
+ }
+ }
+
+ private int getNextVisibleChildIndex(int index) {
+ for (int i = index, count = getChildCount(); i < count; i++) {
+ if (getChildAt(i).getVisibility() == View.VISIBLE) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getMinimumHeight() {
+ return Math.max(mMinimumHeight, super.getMinimumHeight());
+ }
+
+ private void setStacked(boolean stacked) {
+ setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+ setGravity(stacked ? Gravity.END : Gravity.BOTTOM);
+
+ final View spacer = findViewById(R.id.spacer);
+ if (spacer != null) {
+ spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
+ }
+
+ // Reverse the child order. This is specific to the Material button
+ // bar's layout XML and will probably not generalize.
+ final int childCount = getChildCount();
+ for (int i = childCount - 2; i >= 0; i--) {
+ bringChildToFront(getChildAt(i));
+ }
+ }
+
+ private boolean isStacked() {
+ return getOrientation() == LinearLayout.VERTICAL;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 33e5231..4c5875b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,11 +16,12 @@
package com.android.packageinstaller;
-import android.annotation.Nullable;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
+import androidx.annotation.Nullable;
+
import java.io.File;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java b/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java
new file mode 100644
index 0000000..068834c
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.packageinstaller;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+import com.android.packageinstaller.R;
+/**
+ * Used by dialogs to change the font size and number of lines to try to fit
+ * the text to the available space.
+ */
+public class DialogTitle extends TextView {
+
+ public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public DialogTitle(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DialogTitle(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final Layout layout = getLayout();
+ if (layout != null) {
+ final int lineCount = layout.getLineCount();
+ if (lineCount > 0) {
+ final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
+ if (ellipsisCount > 0) {
+ setSingleLine(false);
+ setMaxLines(2);
+
+ final TypedArray a = getContext().obtainStyledAttributes(null,
+ R.styleable.TextAppearance, android.R.attr.textAppearanceMedium,
+ android.R.style.TextAppearance_Medium);
+ final int textSize = a.getDimensionPixelSize(
+ R.styleable.TextAppearance_textSize, 0);
+ if (textSize != 0) {
+ // textSize is already expressed in pixels
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ }
+ a.recycle();
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
index 3a94fdc..8639f47 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
@@ -16,8 +16,6 @@
package com.android.packageinstaller;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
@@ -27,6 +25,9 @@
import android.util.SparseArray;
import android.util.Xml;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -40,17 +41,18 @@
/**
* Persists results of events and calls back observers when a matching result arrives.
*/
-class EventResultPersister {
+public class EventResultPersister {
private static final String LOG_TAG = EventResultPersister.class.getSimpleName();
/** Id passed to {@link #addObserver(int, EventResultObserver)} to generate new id */
- static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
+ public static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
/**
* The extra with the id to set in the intent delivered to
* {@link #onEventReceived(Context, Intent)}
*/
- static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
+ public static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
+ public static final String EXTRA_SERVICE_ID = "EventResultPersister.EXTRA_SERVICE_ID";
/** Persisted state of this object */
private final AtomicFile mResultsFile;
@@ -89,8 +91,8 @@
}
/** Call back when a result is received. Observer is removed when onResult it called. */
- interface EventResultObserver {
- void onResult(int status, int legacyStatus, @Nullable String message);
+ public interface EventResultObserver {
+ void onResult(int status, int legacyStatus, @Nullable String message, int serviceId);
}
/**
@@ -153,12 +155,14 @@
int status = readIntAttribute(parser, "status");
int legacyStatus = readIntAttribute(parser, "legacyStatus");
String statusMessage = readStringAttribute(parser, "statusMessage");
+ int serviceId = readIntAttribute(parser, "serviceId");
if (mResults.get(id) != null) {
throw new Exception("id " + id + " has two results");
}
- mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
+ mResults.put(id, new EventResult(status, legacyStatus, statusMessage,
+ serviceId));
} else {
throw new Exception("unexpected tag");
}
@@ -190,6 +194,7 @@
int id = intent.getIntExtra(EXTRA_ID, 0);
String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
+ int serviceId = intent.getIntExtra(EXTRA_SERVICE_ID, 0);
EventResultObserver observerToCall = null;
synchronized (mLock) {
@@ -204,9 +209,9 @@
}
if (observerToCall != null) {
- observerToCall.onResult(status, legacyStatus, statusMessage);
+ observerToCall.onResult(status, legacyStatus, statusMessage, serviceId);
} else {
- mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
+ mResults.put(id, new EventResult(status, legacyStatus, statusMessage, serviceId));
writeState();
}
}
@@ -258,6 +263,8 @@
serializer.attribute(null, "statusMessage",
results.valueAt(i).message);
}
+ serializer.attribute(null, "serviceId",
+ Integer.toString(results.valueAt(i).serviceId));
serializer.endTag(null, "result");
}
@@ -311,7 +318,8 @@
if (resultIndex >= 0) {
EventResult result = mResults.valueAt(resultIndex);
- observer.onResult(result.status, result.legacyStatus, result.message);
+ observer.onResult(result.status, result.legacyStatus, result.message,
+ result.serviceId);
mResults.removeAt(resultIndex);
writeState();
} else {
@@ -341,13 +349,15 @@
public final int status;
public final int legacyStatus;
@Nullable public final String message;
+ public final int serviceId;
- private EventResult(int status, int legacyStatus, @Nullable String message) {
+ private EventResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
this.status = status;
this.legacyStatus = legacyStatus;
this.message = message;
+ this.serviceId = serviceId;
}
}
- class OutOfIdsException extends Exception {}
+ public class OutOfIdsException extends Exception {}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
index c70d7db..be8eabb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
@@ -16,11 +16,12 @@
package com.android.packageinstaller;
-import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import androidx.annotation.NonNull;
+
/**
* Receives install events and perists them using a {@link EventResultPersister}.
*/
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index 54105bb..3505cfb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -16,7 +16,6 @@
package com.android.packageinstaller;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -32,7 +31,7 @@
import android.util.Log;
import android.view.View;
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
import java.io.File;
@@ -79,6 +78,8 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setFinishOnTouchOutside(true);
+
int statusCode = getIntent().getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 3aa8dbf..93387e2 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,28 +16,22 @@
package com.android.packageinstaller;
-import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
-
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallInfo;
import android.content.pm.PackageManager;
-import android.content.pm.parsing.ApkLiteParseUtils;
-import android.content.pm.parsing.PackageLite;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Process;
import android.util.Log;
import android.view.View;
import android.widget.Button;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.content.InstallLocationUtils;
+import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileInputStream;
@@ -139,34 +133,30 @@
params.setOriginatingUri(getIntent()
.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- UID_UNKNOWN));
+ Process.INVALID_UID));
params.setInstallerPackageName(getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME));
params.setInstallReason(PackageManager.INSTALL_REASON_USER);
File file = new File(mPackageURI.getPath());
try {
- final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
- input.reset(), file, /* flags */ 0);
- if (result.isError()) {
- Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
- Log.e(LOG_TAG,
- "Cannot calculate installed size " + file + ". Try only apk size.");
+ final InstallInfo result = getPackageManager().getPackageInstaller()
+ .getInstallInfo(file, 0);
+ params.setAppPackageName(result.getPackageName());
+ params.setInstallLocation(result.getInstallLocation());
+ try {
+ params.setSize(result.calculateInstalledSize(params));
+ } catch (IOException e) {
+ e.printStackTrace();
params.setSize(file.length());
- } else {
- final PackageLite pkg = result.getResult();
- params.setAppPackageName(pkg.getPackageName());
- params.setInstallLocation(pkg.getInstallLocation());
- params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
- params.abiOverride));
}
- } catch (IOException e) {
+ } catch (PackageInstaller.PackageParsingException e) {
+
+ Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.", e);
Log.e(LOG_TAG,
"Cannot calculate installed size " + file + ". Try only apk size.");
params.setSize(file.length());
}
-
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
@@ -281,8 +271,11 @@
* @param statusCode The installation result.
* @param legacyStatus The installation as used internally in the package manager.
* @param statusMessage The detailed installation result.
+ * @param serviceId Id for PowerManager.WakeLock service. Used only by Wear devices
+ * during an uninstall.
*/
- private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
+ private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage,
+ int serviceId /* ignore */) {
if (statusCode == PackageInstaller.STATUS_SUCCESS) {
launchSuccess();
} else {
@@ -292,7 +285,7 @@
/**
* Send the package to the package installer and then register a event result observer that
- * will call {@link #launchFinishBasedOnResult(int, int, String)}
+ * will call {@link #launchFinishBasedOnResult(int, int, String, int)}
*/
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
@@ -318,6 +311,7 @@
try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
+ long totalRead = 0;
try (OutputStream out = session
.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[1024 * 1024];
@@ -336,8 +330,9 @@
out.write(buffer, 0, numRead);
if (sizeBytes > 0) {
- float fraction = ((float) numRead / (float) sizeBytes);
- session.addProgress(fraction);
+ totalRead += numRead;
+ float fraction = ((float) totalRead / (float) sizeBytes);
+ session.setStagingProgress(fraction);
}
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index b6b8837..68de2f6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -16,7 +16,6 @@
package com.android.packageinstaller;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -31,7 +30,7 @@
import android.util.Log;
import android.view.View;
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
@@ -58,6 +57,7 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setFinishOnTouchOutside(true);
mAlert.setIcon(R.drawable.ic_file_download);
mAlert.setTitle(getString(R.string.app_name_unknown));
mAlert.setView(R.layout.install_content_view);
@@ -123,7 +123,8 @@
* Show an error message and set result as error.
*/
private void showError() {
- (new ErrorDialog()).showAllowingStateLoss(getFragmentManager(), "error");
+ getFragmentManager().beginTransaction()
+ .add(new ErrorDialog(), "error").commitAllowingStateLoss();
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 88c1036..7338e64 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -19,26 +19,25 @@
import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
-import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.RemoteException;
+import android.os.Process;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.util.Arrays;
/**
@@ -80,12 +79,11 @@
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
boolean isTrustedSource = false;
- if (sourceInfo != null
- && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+ if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
- if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
+ if (!isTrustedSource && originatingUid != Process.INVALID_UID) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
@@ -99,6 +97,10 @@
}
}
+ if (sessionId != -1 && !isCallerSessionOwner(originatingUid, sessionId)) {
+ mAbortInstall = true;
+ }
+
final String installerPackageNameFromIntent = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageNameFromIntent != null) {
@@ -208,30 +210,27 @@
}
/**
- * Get the originating uid if possible, or
- * {@link android.content.pm.PackageInstaller.SessionParams#UID_UNKNOWN} if not available
+ * Get the originating uid if possible, or {@link Process#INVALID_UID} if not available
*
* @param sourceInfo The source of this installation
- * @return The UID of the installation source or UID_UNKNOWN
+ * @return The UID of the installation source or INVALID_UID
*/
private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
// The originating uid from the intent. We only trust/use this if it comes from either
// the document manager app or the downloads provider
final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- PackageInstaller.SessionParams.UID_UNKNOWN);
+ Process.INVALID_UID);
final int callingUid;
if (sourceInfo != null) {
callingUid = sourceInfo.uid;
} else {
- try {
- callingUid = ActivityManager.getService()
- .getLaunchedFromUid(getActivityToken());
- } catch (RemoteException ex) {
+ callingUid = getLaunchedFromUid();
+ if (callingUid == Process.INVALID_UID) {
// Cannot reach ActivityManager. Aborting install.
Log.e(LOG_TAG, "Could not determine the launching uid.");
mAbortInstall = true;
- return PackageInstaller.SessionParams.UID_UNKNOWN;
+ return Process.INVALID_UID;
}
}
if (checkPermission(Manifest.permission.MANAGE_DOCUMENTS, -1, callingUid)
@@ -253,7 +252,8 @@
return false;
}
final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
- return (appInfo.isSystemApp() && uid == appInfo.uid);
+ return ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ && uid == appInfo.uid);
}
@NonNull
@@ -268,8 +268,14 @@
try {
return mPackageManager.canPackageQuery(callingPackage, targetPackage);
- } catch (NameNotFoundException e) {
+ } catch (PackageManager.NameNotFoundException e) {
return false;
}
}
+
+ private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
+ PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
+ int installerUid = packageInstaller.getSessionInfo(sessionId).getInstallerUid();
+ return (originatingUid == Process.ROOT_UID) || (originatingUid == installerUid);
+ }
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 38c06dd..73c03a5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -16,7 +16,6 @@
package com.android.packageinstaller;
-import android.annotation.Nullable;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
@@ -30,7 +29,7 @@
import android.view.View;
import android.widget.Button;
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
import java.io.File;
import java.util.List;
@@ -54,6 +53,8 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setFinishOnTouchOutside(true);
+
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
// Return result if requested
Intent result = new Intent();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
index eea12ec..228a9f5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
@@ -16,7 +16,6 @@
package com.android.packageinstaller;
-import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -24,7 +23,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -32,8 +30,11 @@
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+
/**
* A util class that handle and post new app installed notifications.
*/
@@ -107,8 +108,8 @@
@NonNull String packageName) {
CharSequence label = appInfo.loadSafeLabel(context.getPackageManager(),
DEFAULT_MAX_LABEL_SIZE_PX,
- PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE).toString();
+ TextUtils.SAFE_STRING_FLAG_TRIM
+ | TextUtils.SAFE_STRING_FLAG_FIRST_LINE).toString();
if (label != null) {
return label.toString();
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
index 74c7b58..2278f7c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.provider.Settings;
import android.util.Log;
/**
@@ -33,8 +32,7 @@
@Override
public void onReceive(Context context, Intent intent) {
- if (Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, 0) == 0) {
+ if (!context.getPackageManager().shouldShowNewAppInstalledNotification()) {
return;
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index fa93670..3138158 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -21,11 +21,8 @@
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.StringRes;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -36,24 +33,26 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Process;
+import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
import java.io.File;
import java.util.ArrayList;
@@ -86,13 +85,12 @@
private Uri mPackageURI;
private Uri mOriginatingURI;
private Uri mReferrerURI;
- private int mOriginatingUid = PackageInstaller.SessionParams.UID_UNKNOWN;
+ private int mOriginatingUid = Process.INVALID_UID;
private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid
private int mActivityResultCode = Activity.RESULT_CANCELED;
private final boolean mLocalLOGV = false;
PackageManager mPm;
- IPackageManager mIpm;
AppOpsManager mAppOpsManager;
UserManager mUserManager;
PackageInstaller mInstaller;
@@ -166,7 +164,8 @@
DialogFragment newDialog = createDialog(id);
if (newDialog != null) {
- newDialog.showAllowingStateLoss(getFragmentManager(), "dialog");
+ getFragmentManager().beginTransaction()
+ .add(newDialog, "dialog").commitAllowingStateLoss();
}
}
@@ -211,9 +210,9 @@
// Log the fact that the app is requesting an install, and is now allowed to do it
// (before this point we could only log that it's requesting an install, but isn't
// allowed to do it yet).
- int appOpCode =
- AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
- mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid, mOriginatingPackage,
+ String appOpStr =
+ AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
+ mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid, mOriginatingPackage,
mCallingAttributionTag,
"Successfully started package installation activity");
@@ -250,12 +249,9 @@
private boolean isInstallRequestFromUnknownSource(Intent intent) {
if (mCallingPackage != null && intent.getBooleanExtra(
Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
- if (mSourceInfo != null) {
- if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
- != 0) {
- // Privileged apps can bypass unknown sources check if they want.
- return false;
- }
+ if (mSourceInfo != null && mSourceInfo.isPrivilegedApp()) {
+ // Privileged apps can bypass unknown sources check if they want.
+ return false;
}
}
return true;
@@ -305,7 +301,7 @@
@Override
protected void onCreate(Bundle icicle) {
- if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
+ if (mLocalLOGV) Log.i(TAG, "creating for user " + UserHandle.myUserId());
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
super.onCreate(null);
@@ -316,7 +312,6 @@
setFinishOnTouchOutside(true);
mPm = getPackageManager();
- mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
@@ -328,8 +323,8 @@
mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- PackageInstaller.SessionParams.UID_UNKNOWN);
- mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
+ Process.INVALID_UID);
+ mOriginatingPackage = (mOriginatingUid != Process.INVALID_UID)
? getPackageNameForUid(mOriginatingUid) : null;
final Object packageSource;
@@ -337,21 +332,23 @@
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
+ final String resolvedBaseCodePath = intent.getStringExtra(
+ PackageInstaller.EXTRA_RESOLVED_BASE_PATH);
+ if (info == null || !info.isSealed() || resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
- packageSource = Uri.fromFile(new File(info.resolvedBaseCodePath));
+ packageSource = Uri.fromFile(new File(resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- if (info == null || !info.isPreapprovalRequested) {
+ if (info == null || !info.getIsPreApprovalRequested()) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
@@ -547,15 +544,15 @@
return;
}
// Shouldn't use static constant directly, see b/65534401.
- final int appOpCode =
- AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
- final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid,
+ final String appOpStr =
+ AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
+ final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid,
mOriginatingPackage, mCallingAttributionTag,
"Started package installation activity");
if (mLocalLOGV) Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
switch (appOpMode) {
case AppOpsManager.MODE_DEFAULT:
- mAppOpsManager.setMode(appOpCode, mOriginatingUid,
+ mAppOpsManager.setMode(appOpStr, mOriginatingUid,
mOriginatingPackage, AppOpsManager.MODE_ERRORED);
// fall through
case AppOpsManager.MODE_ERRORED:
@@ -588,8 +585,8 @@
switch (scheme) {
case SCHEME_PACKAGE: {
- for (UserInfo info : mUserManager.getUsers()) {
- PackageManager pmForUser = createContextAsUser(info.getUserHandle(), 0)
+ for (UserHandle handle : mUserManager.getUserHandles(true)) {
+ PackageManager pmForUser = createContextAsUser(handle, 0)
.getPackageManager();
try {
if (pmForUser.canPackageQuery(mCallingPackage, packageName)) {
@@ -645,9 +642,9 @@
* @return {@code true} iff the installer could be set up
*/
private boolean processSessionInfo(@NonNull SessionInfo info) {
- mPkgInfo = generateStubPackageInfo(info.appPackageName);
- mAppSnippet = new PackageUtil.AppSnippet(info.appLabel,
- info.appIcon != null ? new BitmapDrawable(getResources(), info.appIcon)
+ mPkgInfo = generateStubPackageInfo(info.getAppPackageName());
+ mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(),
+ info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon())
: getPackageManager().getDefaultActivityIcon());
return true;
}
@@ -693,7 +690,7 @@
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
- if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
+ if (mOriginatingUid != Process.INVALID_UID) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
@@ -829,7 +826,7 @@
if (isDestroyed()) {
return;
}
- getMainThreadHandler().postDelayed(() -> {
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!isDestroyed()) {
startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index f5570df..698159f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -17,14 +17,11 @@
package com.android.packageinstaller;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
@@ -33,6 +30,9 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.io.File;
/**
@@ -131,16 +131,15 @@
public static AppSnippet getAppSnippet(
Activity pContext, ApplicationInfo appInfo, File sourceFile) {
final String archiveFilePath = sourceFile.getAbsolutePath();
- Resources pRes = pContext.getResources();
- AssetManager assmgr = new AssetManager();
- assmgr.addAssetPath(archiveFilePath);
- Resources res = new Resources(assmgr, pRes.getDisplayMetrics(), pRes.getConfiguration());
+ PackageManager pm = pContext.getPackageManager();
+ appInfo.publicSourceDir = archiveFilePath;
+
CharSequence label = null;
// Try to load the label from the package's resources. If an app has not explicitly
// specified any label, just use the package name.
if (appInfo.labelRes != 0) {
try {
- label = res.getText(appInfo.labelRes);
+ label = appInfo.loadLabel(pm);
} catch (Resources.NotFoundException e) {
}
}
@@ -154,7 +153,7 @@
try {
if (appInfo.icon != 0) {
try {
- icon = res.getDrawable(appInfo.icon);
+ icon = appInfo.loadIcon(pm);
} catch (Resources.NotFoundException e) {
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
index f77318c..afb2ea4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
@@ -16,13 +16,14 @@
package com.android.packageinstaller;
-import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import java.io.File;
import java.io.IOException;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
index c3e9c23..86b0321 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
@@ -16,11 +16,12 @@
package com.android.packageinstaller;
-import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import androidx.annotation.NonNull;
+
/**
* Receives uninstall events and persists them using a {@link EventResultPersister}.
*/
@@ -58,7 +59,7 @@
*
* @return The id for this event
*/
- static int addObserver(@NonNull Context context, int id,
+ public static int addObserver(@NonNull Context context, int id,
@NonNull EventResultPersister.EventResultObserver observer)
throws EventResultPersister.OutOfIdsException {
return getReceiver(context).addObserver(id, observer);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
index 973ab89..b9552fc 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
@@ -16,29 +16,27 @@
package com.android.packageinstaller;
-import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.admin.IDevicePolicyManager;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.graphics.drawable.Icon;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+
import java.util.List;
/**
@@ -94,28 +92,24 @@
switch (legacyStatus) {
case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
- IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
// Find out if the package is an active admin for some non-current user.
- int myUserId = UserHandle.myUserId();
- UserInfo otherBlockingUser = null;
- for (UserInfo user : userManager.getUsers()) {
+ UserHandle myUserHandle = Process.myUserHandle();
+ UserHandle otherBlockingUserHandle = null;
+ for (UserHandle otherUserHandle : userManager.getUserHandles(true)) {
// We only catch the case when the user in question is neither the
// current user nor its profile.
- if (isProfileOfOrSame(userManager, myUserId, user.id)) {
+ if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
continue;
}
-
- try {
- if (dpm.packageHasActiveAdmins(appInfo.packageName, user.id)) {
- otherBlockingUser = user;
- break;
- }
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Failed to talk to package manager", e);
+ DevicePolicyManager dpm =
+ context.createContextAsUser(otherUserHandle, 0)
+ .getSystemService(DevicePolicyManager.class);
+ if (dpm.packageHasActiveAdmins(appInfo.packageName)) {
+ otherBlockingUserHandle = otherUserHandle;
+ break;
}
}
- if (otherBlockingUser == null) {
+ if (otherBlockingUserHandle == null) {
Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
+ " is a device admin");
@@ -124,46 +118,41 @@
R.string.uninstall_failed_device_policy_manager));
} else {
Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
- + " is a device admin of user " + otherBlockingUser);
+ + " is a device admin of user " + otherBlockingUserHandle);
+ String userName =
+ context.createContextAsUser(otherBlockingUserHandle, 0)
+ .getSystemService(UserManager.class).getUserName();
setBigText(uninstallFailedNotification, String.format(context.getString(
R.string.uninstall_failed_device_policy_manager_of_user),
- otherBlockingUser.name));
+ userName));
}
break;
}
case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
- IPackageManager packageManager = IPackageManager.Stub.asInterface(
- ServiceManager.getService("package"));
-
- List<UserInfo> users = userManager.getUsers();
- int blockingUserId = UserHandle.USER_NULL;
- for (int i = 0; i < users.size(); ++i) {
- final UserInfo user = users.get(i);
- try {
- if (packageManager.getBlockUninstallForUser(appInfo.packageName,
- user.id)) {
- blockingUserId = user.id;
- break;
- }
- } catch (RemoteException e) {
- // Shouldn't happen.
- Log.e(LOG_TAG, "Failed to talk to package manager", e);
+ PackageManager packageManager = context.getPackageManager();
+ List<UserHandle> userHandles = userManager.getUserHandles(true);
+ UserHandle otherBlockingUserHandle = null;
+ for (int i = 0; i < userHandles.size(); ++i) {
+ final UserHandle handle = userHandles.get(i);
+ if (packageManager.canUserUninstall(appInfo.packageName, handle)) {
+ otherBlockingUserHandle = handle;
+ break;
}
}
- int myUserId = UserHandle.myUserId();
- if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
+ UserHandle myUserHandle = Process.myUserHandle();
+ if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) {
addDeviceManagerButton(context, uninstallFailedNotification);
} else {
addManageUsersButton(context, uninstallFailedNotification);
}
- if (blockingUserId == UserHandle.USER_NULL) {
+ if (otherBlockingUserHandle == null) {
Log.d(LOG_TAG,
"Uninstall failed for " + appInfo.packageName + " with code "
+ returnCode + " no blocking user");
- } else if (blockingUserId == UserHandle.USER_SYSTEM) {
+ } else if (otherBlockingUserHandle == UserHandle.SYSTEM) {
setBigText(uninstallFailedNotification,
context.getString(R.string.uninstall_blocked_device_owner));
} else {
@@ -200,18 +189,18 @@
* Is a profile part of a user?
*
* @param userManager The user manager
- * @param userId The id of the user
- * @param profileId The id of the profile
+ * @param userHandle The handle of the user
+ * @param profileHandle The handle of the profile
*
* @return If the profile is part of the user or the profile parent of the user
*/
- private boolean isProfileOfOrSame(@NonNull UserManager userManager, int userId, int profileId) {
- if (userId == profileId) {
+ private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
+ UserHandle profileHandle) {
+ if (userHandle.equals(profileHandle)) {
return true;
}
-
- UserInfo parentUser = userManager.getProfileParent(profileId);
- return parentUser != null && parentUser.id == userId;
+ return userManager.getProfileParent(profileHandle) != null
+ && userManager.getProfileParent(profileHandle).equals(userHandle);
}
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index 1485352..b60aba8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -16,9 +16,7 @@
package com.android.packageinstaller;
-import android.annotation.Nullable;
import android.app.Activity;
-import android.app.ActivityThread;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -27,19 +25,18 @@
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+
/**
* Start an uninstallation, show a dialog while uninstalling and return result to the caller.
*/
@@ -57,7 +54,7 @@
private int mUninstallId;
private ApplicationInfo mAppInfo;
- private IBinder mCallback;
+ private PackageManager.UninstallCompleteCallback mCallback;
private boolean mReturnResult;
private String mLabel;
@@ -68,7 +65,8 @@
setFinishOnTouchOutside(false);
mAppInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
- mCallback = getIntent().getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+ mCallback = getIntent().getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+ PackageManager.UninstallCompleteCallback.class);
mReturnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
mLabel = getIntent().getStringExtra(EXTRA_APP_LABEL);
@@ -119,15 +117,10 @@
int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
- try {
- ActivityThread.getPackageManager().getPackageInstaller().uninstall(
- new VersionedPackage(mAppInfo.packageName,
- PackageManager.VERSION_CODE_HIGHEST),
- getPackageName(), flags, pendingIntent.getIntentSender(),
- user.getIdentifier());
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ getPackageManager().getPackageInstaller().uninstall(
+ new VersionedPackage(mAppInfo.packageName,
+ PackageManager.VERSION_CODE_HIGHEST),
+ flags, pendingIntent.getIntentSender());
} else {
mUninstallId = savedInstanceState.getInt(UNINSTALL_ID);
UninstallEventReceiver.addObserver(this, mUninstallId, this);
@@ -135,7 +128,7 @@
} catch (EventResultPersister.OutOfIdsException | IllegalArgumentException e) {
Log.e(LOG_TAG, "Fails to start uninstall", e);
onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
- null);
+ null, 0);
}
}
@@ -152,15 +145,10 @@
}
@Override
- public void onResult(int status, int legacyStatus, @Nullable String message) {
+ public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
if (mCallback != null) {
// The caller will be informed about the result via a callback
- final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
- .asInterface(mCallback);
- try {
- observer.onPackageDeleted(mAppInfo.packageName, legacyStatus, message);
- } catch (RemoteException ignored) {
- }
+ mCallback.onUninstallComplete(mAppInfo.packageName, legacyStatus, message);
} else if (mReturnResult) {
// The caller will be informed about the result and might decide to display it
Intent result = new Intent();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 0198168..04496b9 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -22,12 +22,7 @@
import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.StringRes;
import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.DialogFragment;
import android.app.Fragment;
@@ -37,12 +32,9 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver2;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
@@ -50,13 +42,14 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
import com.android.packageinstaller.handheld.ErrorDialogFragment;
import com.android.packageinstaller.handheld.UninstallAlertDialogFragment;
import com.android.packageinstaller.television.ErrorFragment;
@@ -80,7 +73,7 @@
public ActivityInfo activityInfo;
public boolean allUsers;
public UserHandle user;
- public IBinder callback;
+ public PackageManager.UninstallCompleteCallback callback;
}
private String mPackageName;
@@ -94,44 +87,8 @@
// be stale, if e.g. the app was uninstalled while the activity was destroyed.
super.onCreate(null);
- try {
- int callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken());
-
- String callingPackage = getPackageNameForUid(callingUid);
- if (callingPackage == null) {
- Log.e(TAG, "Package not found for originating uid " + callingUid);
- setResult(Activity.RESULT_FIRST_USER);
- finish();
- return;
- } else {
- AppOpsManager appOpsManager = (AppOpsManager) getSystemService(
- Context.APP_OPS_SERVICE);
- if (appOpsManager.noteOpNoThrow(
- AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
- != MODE_ALLOWED) {
- Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
- setResult(Activity.RESULT_FIRST_USER);
- finish();
- return;
- }
- }
-
- if (getMaxTargetSdkVersionForUid(this, callingUid)
- >= Build.VERSION_CODES.P && AppGlobals.getPackageManager().checkUidPermission(
- Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid)
- != PackageManager.PERMISSION_GRANTED
- && AppGlobals.getPackageManager().checkUidPermission(
- Manifest.permission.DELETE_PACKAGES, callingUid)
- != PackageManager.PERMISSION_GRANTED) {
- Log.e(TAG, "Uid " + callingUid + " does not have "
- + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
- + Manifest.permission.DELETE_PACKAGES);
-
- setResult(Activity.RESULT_FIRST_USER);
- finish();
- return;
- }
- } catch (RemoteException ex) {
+ int callingUid = getLaunchedFromUid();
+ if (callingUid == Process.INVALID_UID) {
// Cannot reach Package/ActivityManager. Aborting uninstall.
Log.e(TAG, "Could not determine the launching uid.");
@@ -140,6 +97,38 @@
return;
}
+ String callingPackage = getPackageNameForUid(callingUid);
+ if (callingPackage == null) {
+ Log.e(TAG, "Package not found for originating uid " + callingUid);
+ setResult(Activity.RESULT_FIRST_USER);
+ finish();
+ return;
+ } else {
+ AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+ if (appOpsManager.noteOpNoThrow(
+ AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
+ != MODE_ALLOWED) {
+ Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
+ setResult(Activity.RESULT_FIRST_USER);
+ finish();
+ return;
+ }
+ }
+
+ if (getMaxTargetSdkVersionForUid(this, callingUid) >= Build.VERSION_CODES.P
+ && getBaseContext().checkPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
+ 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED
+ && getBaseContext().checkPermission(Manifest.permission.DELETE_PACKAGES,
+ 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED) {
+ Log.e(TAG, "Uid " + callingUid + " does not have "
+ + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
+ + Manifest.permission.DELETE_PACKAGES);
+
+ setResult(Activity.RESULT_FIRST_USER);
+ finish();
+ return;
+ }
+
// Get intent information.
// We expect an intent with URI of the form package://<packageName>#<className>
// className is optional; if specified, it is the activity the user chose to uninstall
@@ -157,37 +146,37 @@
return;
}
- final IPackageManager pm = IPackageManager.Stub.asInterface(
- ServiceManager.getService("package"));
+ PackageManager pm = getPackageManager();
+ UserManager userManager = getBaseContext().getSystemService(UserManager.class);
mDialogInfo = new DialogInfo();
mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
- if (mDialogInfo.allUsers && !UserManager.get(this).isAdminUser()) {
+ if (mDialogInfo.allUsers && !userManager.isAdminUser()) {
Log.e(TAG, "Only admin user can request uninstall for all users");
showUserIsNotAllowed();
return;
}
mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER);
if (mDialogInfo.user == null) {
- mDialogInfo.user = android.os.Process.myUserHandle();
+ mDialogInfo.user = Process.myUserHandle();
} else {
- UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
List<UserHandle> profiles = userManager.getUserProfiles();
if (!profiles.contains(mDialogInfo.user)) {
- Log.e(TAG, "User " + android.os.Process.myUserHandle() + " can't request uninstall "
+ Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
+ "for user " + mDialogInfo.user);
showUserIsNotAllowed();
return;
}
}
- mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+ mDialogInfo.callback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+ PackageManager.UninstallCompleteCallback.class);
try {
mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName,
- PackageManager.MATCH_ANY_USER, mDialogInfo.user.getIdentifier());
- } catch (RemoteException e) {
+ PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
+ } catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to get packageName. Package manager is dead?");
}
@@ -202,9 +191,9 @@
if (className != null) {
try {
mDialogInfo.activityInfo = pm.getActivityInfo(
- new ComponentName(mPackageName, className), 0,
- mDialogInfo.user.getIdentifier());
- } catch (RemoteException e) {
+ new ComponentName(mPackageName, className),
+ PackageManager.ComponentInfoFlags.of(0));
+ } catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to get className. Package manager is dead?");
// Continue as the ActivityInfo isn't critical.
}
@@ -315,7 +304,6 @@
newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);
newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
-
if (returnResult) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
@@ -366,11 +354,10 @@
int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
- ActivityThread.getPackageManager().getPackageInstaller().uninstall(
+ getPackageManager().getPackageInstaller().uninstall(
new VersionedPackage(mDialogInfo.appInfo.packageName,
PackageManager.VERSION_CODE_HIGHEST),
- getPackageName(), flags, pendingIntent.getIntentSender(),
- mDialogInfo.user.getIdentifier());
+ flags, pendingIntent.getIntentSender());
} catch (Exception e) {
notificationManager.cancel(uninstallId);
@@ -382,13 +369,8 @@
public void dispatchAborted() {
if (mDialogInfo != null && mDialogInfo.callback != null) {
- final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub.asInterface(
- mDialogInfo.callback);
- try {
- observer.onPackageDeleted(mPackageName,
- PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
- } catch (RemoteException ignored) {
- }
+ mDialogInfo.callback.onUninstallComplete(mPackageName,
+ PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index a1bc992..21f4be0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -16,10 +16,10 @@
package com.android.packageinstaller.handheld;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.text.format.Formatter.formatFileSize;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -29,7 +29,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
@@ -41,6 +40,9 @@
import android.widget.CheckBox;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.packageinstaller.R;
import com.android.packageinstaller.UninstallerActivity;
@@ -91,11 +93,11 @@
long appDataSize = 0;
if (user == null) {
- List<UserInfo> users = userManager.getUsers();
+ List<UserHandle> userHandles = userManager.getUserHandles(true);
- int numUsers = users.size();
+ int numUsers = userHandles.size();
for (int i = 0; i < numUsers; i++) {
- appDataSize += getAppDataSizeForUser(pkg, UserHandle.of(users.get(i).id));
+ appDataSize += getAppDataSizeForUser(pkg, userHandles.get(i));
}
} else {
appDataSize = getAppDataSizeForUser(pkg, user);
@@ -128,7 +130,7 @@
final boolean isUpdate =
((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
final UserHandle myUserHandle = Process.myUserHandle();
- UserManager userManager = UserManager.get(getActivity());
+ UserManager userManager = getContext().getSystemService(UserManager.class);
if (isUpdate) {
if (isSingleUser(userManager)) {
messageBuilder.append(getString(R.string.uninstall_update_text));
@@ -139,20 +141,25 @@
if (dialogInfo.allUsers && !isSingleUser(userManager)) {
messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
} else if (!dialogInfo.user.equals(myUserHandle)) {
- UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
- if (userInfo.isManagedProfile()
- && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+ int userId = dialogInfo.user.getIdentifier();
+ UserManager customUserManager = getContext()
+ .createContextAsUser(UserHandle.of(userId), 0)
+ .getSystemService(UserManager.class);
+ String userName = customUserManager.getUserName();
+
+ if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
+ && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
messageBuilder.append(
getString(R.string.uninstall_application_text_current_user_work_profile,
- userInfo.name));
- } else if (userInfo.isCloneProfile()
- && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+ userName));
+ } else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
+ && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
isClonedApp = true;
messageBuilder.append(getString(
R.string.uninstall_application_text_current_user_clone_profile));
} else {
messageBuilder.append(
- getString(R.string.uninstall_application_text_user, userInfo.name));
+ getString(R.string.uninstall_application_text_user, userName));
}
} else if (isCloneProfile(myUserHandle)) {
isClonedApp = true;
@@ -238,8 +245,8 @@
// Check if another instance of given package exists in clone user profile.
if (cloneUser != null) {
try {
- if (getContext().getPackageManager()
- .getPackageUidAsUser(packageName, cloneUser.getIdentifier()) > 0) {
+ if (getContext().getPackageManager().getPackageUidAsUser(packageName,
+ PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
@@ -273,7 +280,6 @@
*/
private boolean isSingleUser(UserManager userManager) {
final int userCount = userManager.getUserCount();
- return userCount == 1
- || (UserManager.isSplitSystemUser() && userCount == 2);
+ return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 2d241ca..5c5720a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -16,10 +16,11 @@
package com.android.packageinstaller.television;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+
import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
@@ -63,7 +64,7 @@
final boolean isUpdate =
((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
final UserHandle myUserHandle = Process.myUserHandle();
- UserManager userManager = UserManager.get(getActivity());
+ UserManager userManager = getContext().getSystemService(UserManager.class);
if (isUpdate) {
if (isSingleUser(userManager)) {
messageBuilder.append(getString(R.string.uninstall_update_text));
@@ -74,15 +75,21 @@
if (dialogInfo.allUsers && !isSingleUser(userManager)) {
messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
} else if (!dialogInfo.user.equals(myUserHandle)) {
- UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
- if (userInfo.isManagedProfile()
- && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+ int userId = dialogInfo.user.getIdentifier();
+ UserManager customUserManager = getContext()
+ .createContextAsUser(UserHandle.of(userId), 0)
+ .getSystemService(UserManager.class);
+ String userName = customUserManager.getUserName();
+
+ if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
+ && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
+
messageBuilder.append(
getString(R.string.uninstall_application_text_current_user_work_profile,
- userInfo.name));
+ userName));
} else {
messageBuilder.append(
- getString(R.string.uninstall_application_text_user, userInfo.name));
+ getString(R.string.uninstall_application_text_user, userName));
}
} else {
messageBuilder.append(getString(R.string.uninstall_application_text));
@@ -126,7 +133,6 @@
*/
private boolean isSingleUser(UserManager userManager) {
final int userCount = userManager.getUserCount();
- return userCount == 1
- || (UserManager.isSplitSystemUser() && userCount == 2);
+ return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
index a4f217c..0c59d44 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
@@ -17,24 +17,20 @@
package com.android.packageinstaller.television;
import android.app.Activity;
-import android.app.admin.IDevicePolicyManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageDeleteObserver2;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -42,8 +38,12 @@
import android.view.KeyEvent;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+
+import com.android.packageinstaller.EventResultPersister;
import com.android.packageinstaller.PackageUtil;
import com.android.packageinstaller.R;
+import com.android.packageinstaller.UninstallEventReceiver;
import java.lang.ref.WeakReference;
import java.util.List;
@@ -55,14 +55,17 @@
* by an intent with the intent's class name explicitly set to UninstallAppProgress and expects
* the application object of the application to uninstall.
*/
-public class UninstallAppProgress extends Activity {
+public class UninstallAppProgress extends Activity implements
+ EventResultPersister.EventResultObserver {
private static final String TAG = "UninstallAppProgress";
private static final String FRAGMENT_TAG = "progress_fragment";
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
private ApplicationInfo mAppInfo;
private boolean mAllUsers;
- private IBinder mCallback;
+ private PackageManager.UninstallCompleteCallback mCallback;
private volatile int mResultCode = -1;
@@ -116,13 +119,7 @@
final String packageName = (String) msg.obj;
if (mCallback != null) {
- final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
- .asInterface(mCallback);
- try {
- observer.onPackageDeleted(mAppInfo.packageName, mResultCode,
- packageName);
- } catch (RemoteException ignored) {
- }
+ mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, packageName);
finish();
return;
}
@@ -139,37 +136,34 @@
// Update the status text
final String statusText;
+ Context ctx = getBaseContext();
switch (msg.arg1) {
case PackageManager.DELETE_SUCCEEDED:
statusText = getString(R.string.uninstall_done);
// Show a Toast and finish the activity
- Context ctx = getBaseContext();
Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show();
setResultAndFinish();
return;
case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
UserManager userManager =
(UserManager) getSystemService(Context.USER_SERVICE);
- IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
// Find out if the package is an active admin for some non-current user.
- int myUserId = UserHandle.myUserId();
- UserInfo otherBlockingUser = null;
- for (UserInfo user : userManager.getUsers()) {
+ UserHandle myUserHandle = Process.myUserHandle();
+ UserHandle otherBlockingUserHandle = null;
+ for (UserHandle otherUserHandle : userManager.getUserHandles(true)) {
// We only catch the case when the user in question is neither the
// current user nor its profile.
- if (isProfileOfOrSame(userManager, myUserId, user.id)) continue;
-
- try {
- if (dpm.packageHasActiveAdmins(packageName, user.id)) {
- otherBlockingUser = user;
- break;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to talk to package manager", e);
+ if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
+ continue;
+ }
+ DevicePolicyManager dpm = ctx.createContextAsUser(otherUserHandle, 0)
+ .getSystemService(DevicePolicyManager.class);
+ if (dpm.packageHasActiveAdmins(packageName)) {
+ otherBlockingUserHandle = otherUserHandle;
+ break;
}
}
- if (otherBlockingUser == null) {
+ if (otherBlockingUserHandle == null) {
Log.d(TAG, "Uninstall failed because " + packageName
+ " is a device admin");
getProgressFragment().setDeviceManagerButtonVisible(true);
@@ -177,45 +171,40 @@
R.string.uninstall_failed_device_policy_manager);
} else {
Log.d(TAG, "Uninstall failed because " + packageName
- + " is a device admin of user " + otherBlockingUser);
+ + " is a device admin of user " + otherBlockingUserHandle);
getProgressFragment().setDeviceManagerButtonVisible(false);
+ String userName = ctx.createContextAsUser(otherBlockingUserHandle, 0)
+ .getSystemService(UserManager.class).getUserName();
statusText = String.format(
getString(R.string.uninstall_failed_device_policy_manager_of_user),
- otherBlockingUser.name);
+ userName);
}
break;
}
case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
UserManager userManager =
(UserManager) getSystemService(Context.USER_SERVICE);
- IPackageManager packageManager = IPackageManager.Stub.asInterface(
- ServiceManager.getService("package"));
- List<UserInfo> users = userManager.getUsers();
- int blockingUserId = UserHandle.USER_NULL;
- for (int i = 0; i < users.size(); ++i) {
- final UserInfo user = users.get(i);
- try {
- if (packageManager.getBlockUninstallForUser(packageName,
- user.id)) {
- blockingUserId = user.id;
- break;
- }
- } catch (RemoteException e) {
- // Shouldn't happen.
- Log.e(TAG, "Failed to talk to package manager", e);
+ PackageManager packageManager = ctx.getPackageManager();
+ List<UserHandle> userHandles = userManager.getUserHandles(true);
+ UserHandle otherBlockingUserHandle = null;
+ for (int i = 0; i < userHandles.size(); ++i) {
+ final UserHandle handle = userHandles.get(i);
+ if (packageManager.canUserUninstall(packageName, handle)) {
+ otherBlockingUserHandle = handle;
+ break;
}
}
- int myUserId = UserHandle.myUserId();
- if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
+ UserHandle myUserHandle = Process.myUserHandle();
+ if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) {
getProgressFragment().setDeviceManagerButtonVisible(true);
} else {
getProgressFragment().setDeviceManagerButtonVisible(false);
getProgressFragment().setUsersButtonVisible(true);
}
// TODO: b/25442806
- if (blockingUserId == UserHandle.USER_SYSTEM) {
+ if (otherBlockingUserHandle == UserHandle.SYSTEM) {
statusText = getString(R.string.uninstall_blocked_device_owner);
- } else if (blockingUserId == UserHandle.USER_NULL) {
+ } else if (otherBlockingUserHandle == null) {
Log.d(TAG, "Uninstall failed for " + packageName + " with code "
+ msg.arg1 + " no blocking user");
statusText = getString(R.string.uninstall_failed);
@@ -239,12 +228,13 @@
}
}
- private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) {
- if (userId == profileId) {
+ private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
+ UserHandle profileHandle) {
+ if (userHandle.equals(profileHandle)) {
return true;
}
- UserInfo parentUser = userManager.getProfileParent(profileId);
- return parentUser != null && parentUser.id == userId;
+ return userManager.getProfileParent(profileHandle) != null
+ && userManager.getProfileParent(profileHandle).equals(userHandle);
}
@Override
@@ -253,7 +243,8 @@
Intent intent = getIntent();
mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
- mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+ mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+ PackageManager.UninstallCompleteCallback.class);
// This currently does not support going through a onDestroy->onCreate cycle. Hence if that
// happened, just fail the operation for mysterious reasons.
@@ -261,12 +252,7 @@
mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
if (mCallback != null) {
- final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
- .asInterface(mCallback);
- try {
- observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null);
- } catch (RemoteException ignored) {
- }
+ mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, null);
finish();
} else {
setResultAndFinish();
@@ -278,10 +264,9 @@
mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
if (user == null) {
- user = android.os.Process.myUserHandle();
+ user = Process.myUserHandle();
}
- PackageDeleteObserver observer = new PackageDeleteObserver();
// Make window transparent until initView is called. In many cases we can avoid showing the
// UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove
@@ -291,11 +276,29 @@
getWindow().setNavigationBarColor(Color.TRANSPARENT);
try {
- getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer,
- mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, user.getIdentifier());
+ int uninstallId = UninstallEventReceiver.addObserver(this,
+ EventResultPersister.GENERATE_NEW_ID, this);
+
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+ broadcastIntent.setPackage(getPackageName());
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+ broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+
+ createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall(
+ new VersionedPackage(mAppInfo.packageName, PackageManager.VERSION_CODE_HIGHEST),
+ mAllUsers ? PackageManager.DELETE_ALL_USERS : 0,
+ pendingIntent.getIntentSender());
} catch (IllegalArgumentException e) {
// Couldn't find the package, no need to call uninstall.
Log.w(TAG, "Could not find package, not deleting " + mAppInfo.packageName, e);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ Log.e(TAG, "Fails to start uninstall", e);
+ onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+ null, 0);
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW),
@@ -306,13 +309,12 @@
return mAppInfo;
}
- private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
- public void packageDeleted(String packageName, int returnCode) {
- Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
- msg.arg1 = returnCode;
- msg.obj = packageName;
- mHandler.sendMessage(msg);
- }
+ @Override
+ public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+ Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
+ msg.arg1 = legacyStatus;
+ msg.obj = mAppInfo.packageName;
+ mHandler.sendMessage(msg);
}
public void setResultAndFinish() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
index af6d9c5..c2d95b2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
@@ -16,7 +16,6 @@
package com.android.packageinstaller.television;
-import android.annotation.Nullable;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
@@ -28,6 +27,8 @@
import android.widget.Button;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.packageinstaller.PackageUtil;
import com.android.packageinstaller.R;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
index 063d789..8dd691d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -265,7 +265,7 @@
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(action);
mContext.registerReceiver(broadcastReceiver, intentFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ Context.RECEIVER_EXPORTED);
// Create a matching PendingIntent and use it to generate the IntentSender
Intent broadcastIntent = new Intent(action);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
index 06b1c16..959257f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -19,14 +19,16 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.FeatureInfo;
-import android.content.pm.IPackageDeleteObserver;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
@@ -43,13 +45,18 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
+
import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.EventResultPersister;
import com.android.packageinstaller.PackageUtil;
import com.android.packageinstaller.R;
+import com.android.packageinstaller.UninstallEventReceiver;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -80,16 +87,30 @@
* adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
* com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
*/
-public class WearPackageInstallerService extends Service {
+public class WearPackageInstallerService extends Service
+ implements EventResultPersister.EventResultObserver {
private static final String TAG = "WearPkgInstallerService";
private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
private final int START_INSTALL = 1;
private final int START_UNINSTALL = 2;
private int mInstallNotificationId = 1;
private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+ private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+ private class UninstallParams {
+ public String mPackageName;
+ public PowerManager.WakeLock mLock;
+
+ UninstallParams(String packageName, PowerManager.WakeLock lock) {
+ mPackageName = packageName;
+ mLock = lock;
+ }
+ }
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
@@ -211,7 +232,6 @@
}
final PackageManager pm = getPackageManager();
File tempFile = null;
- int installFlags = 0;
PowerManager.WakeLock lock = getLock(this.getApplicationContext());
boolean messageSent = false;
try {
@@ -220,17 +240,14 @@
existingPkgInfo = pm.getPackageInfo(packageName,
PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
if (existingPkgInfo != null) {
- installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Replacing package:" + packageName);
+ }
}
} catch (PackageManager.NameNotFoundException e) {
// Ignore this exception. We could not find the package, will treat as a new
// installation.
}
- if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Replacing package:" + packageName);
- }
- }
// TODO(28021618): This was left as a temp file due to the fact that this code is being
// deprecated and that we need the bare minimum to continue working moving forward
// If this code is used as reference, this permission logic might want to be
@@ -366,21 +383,60 @@
final String packageName = WearPackageArgs.getPackageName(argsBundle);
PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+ UninstallParams params = new UninstallParams(packageName, lock);
+ mServiceIdToParams.put(startId, params);
+
final PackageManager pm = getPackageManager();
try {
PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
getLabelAndUpdateNotification(packageName,
getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
+ int uninstallId = UninstallEventReceiver.addObserver(this,
+ EventResultPersister.GENERATE_NEW_ID, this);
+
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+ broadcastIntent.setPackage(getPackageName());
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+ broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+
// Found package, send uninstall request.
- pm.deletePackage(packageName, new PackageDeleteObserver(lock, startId),
- PackageManager.DELETE_ALL_USERS);
+ pm.getPackageInstaller().uninstall(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ PackageManager.DELETE_ALL_USERS,
+ pendingIntent.getIntentSender());
Log.i(TAG, "Sent delete request for " + packageName);
} catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
// Couldn't find the package, no need to call uninstall.
Log.w(TAG, "Could not find package, not deleting " + packageName, e);
finishService(lock, startId);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ Log.e(TAG, "Fails to start uninstall", e);
+ finishService(lock, startId);
+ }
+ }
+
+ @Override
+ public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+ if (mServiceIdToParams.containsKey(serviceId)) {
+ UninstallParams params = mServiceIdToParams.get(serviceId);
+ try {
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+ } else {
+ Log.e(TAG, "Package uninstall failed " + params.mPackageName
+ + ", returnCode " + legacyStatus);
+ }
+ } finally {
+ finishService(params.mLock, serviceId);
+ }
}
}
@@ -537,29 +593,6 @@
}
}
- private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
- private PowerManager.WakeLock mWakeLock;
- private int mStartId;
-
- private PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId) {
- mWakeLock = wakeLock;
- mStartId = startId;
- }
-
- public void packageDeleted(String packageName, int returnCode) {
- try {
- if (returnCode >= 0) {
- Log.i(TAG, "Package " + packageName + " was uninstalled.");
- } else {
- Log.e(TAG, "Package uninstall failed " + packageName + ", returnCode " +
- returnCode);
- }
- } finally {
- finishService(mWakeLock, mStartId);
- }
- }
- }
-
private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
final String title) {
int notifId;
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index c08169e..e878804 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -41,6 +41,7 @@
context: Context,
private val app: ApplicationInfo,
private val op: Int,
+ private val setModeByUid: Boolean = false,
) : IAppOpsController {
private val appOpsManager = context.appOpsManager
@@ -49,7 +50,11 @@
override fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
- appOpsManager.setMode(op, app.uid, app.packageName, mode)
+ if (setModeByUid) {
+ appOpsManager.setUidMode(op, app.uid, mode)
+ } else {
+ appOpsManager.setMode(op, app.uid, app.packageName, mode)
+ }
_mode.postValue(mode)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index a357832..ee21b81 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -49,6 +49,13 @@
abstract val appOp: Int
abstract val permission: String
+ /**
+ * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
+ *
+ * Security related app-ops should be set with setUidMode() instead of setMode().
+ */
+ open val setModeByUid = false
+
/** These not changeable packages will also be hidden from app list. */
private val notChangeablePackages =
setOf("android", "com.android.systemui", context.packageName)
@@ -61,7 +68,7 @@
AppOpPermissionRecord(
app = app,
hasRequestPermission = app.packageName in packageNames,
- appOpsController = AppOpsController(context = context, app = app, op = appOp),
+ appOpsController = createAppOpsController(app),
)
}
}
@@ -69,7 +76,14 @@
override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
app = app,
hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
- appOpsController = AppOpsController(context = context, app = app, op = appOp),
+ appOpsController = createAppOpsController(app),
+ )
+
+ private fun createAppOpsController(app: ApplicationInfo) = AppOpsController(
+ context = context,
+ app = app,
+ op = appOp,
+ setModeByUid = setModeByUid,
)
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
new file mode 100644
index 0000000..668bfdf
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_ERRORED
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppOpsControllerTest {
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var appOpsManager: AppOpsManager
+
+ @Before
+ fun setUp() {
+ whenever(context.appOpsManager).thenReturn(appOpsManager)
+ }
+
+ @Test
+ fun setAllowed_setToTrue() {
+ val controller = AppOpsController(context = context, app = APP, op = OP)
+
+ controller.setAllowed(true)
+
+ verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, MODE_ALLOWED)
+ }
+
+ @Test
+ fun setAllowed_setToFalse() {
+ val controller = AppOpsController(context = context, app = APP, op = OP)
+
+ controller.setAllowed(false)
+
+ verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, MODE_ERRORED)
+ }
+
+ @Test
+ fun setAllowed_setToTrueByUid() {
+ val controller =
+ AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+
+ controller.setAllowed(true)
+
+ verify(appOpsManager).setUidMode(OP, APP.uid, MODE_ALLOWED)
+ }
+
+ @Test
+ fun setAllowed_setToFalseByUid() {
+ val controller =
+ AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+
+ controller.setAllowed(false)
+
+ verify(appOpsManager).setUidMode(OP, APP.uid, MODE_ERRORED)
+ }
+
+ @Test
+ fun getMode() {
+ whenever(
+ appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName)
+ ).thenReturn(MODE_ALLOWED)
+ val controller = AppOpsController(context = context, app = APP, op = OP)
+
+ val mode = controller.getMode()
+
+ assertThat(mode).isEqualTo(MODE_ALLOWED)
+ }
+
+ private companion object {
+ const val OP = 1
+ val APP = ApplicationInfo().apply {
+ packageName = "package.name"
+ uid = 123
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index cd9c048..966b869 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -25,6 +25,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
import com.android.settingslib.spaprivileged.model.app.IAppOpsController
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.test.R
@@ -37,6 +38,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.Mockito.`when` as whenever
@@ -50,17 +53,20 @@
@get:Rule
val composeTestRule = createComposeRule()
+ @Spy
private val context: Context = ApplicationProvider.getApplicationContext()
@Mock
private lateinit var packageManagers: IPackageManagers
+ @Mock
+ private lateinit var appOpsManager: AppOpsManager
+
private lateinit var listModel: TestAppOpPermissionAppListModel
@Before
- fun setUp() = runTest {
- whenever(packageManagers.getAppOpPermissionPackages(USER_ID, PERMISSION))
- .thenReturn(emptySet())
+ fun setUp() {
+ whenever(context.appOpsManager).thenReturn(appOpsManager)
listModel = TestAppOpPermissionAppListModel()
}
@@ -221,6 +227,16 @@
assertThat(appOpsController.setAllowedCalledWith).isTrue()
}
+ @Test
+ fun setAllowed_setModeByUid() {
+ listModel.setModeByUid = true
+ val record = listModel.transformItem(APP)
+
+ listModel.setAllowed(record = record, newAllowed = true)
+
+ verify(appOpsManager).setUidMode(listModel.appOp, APP.uid, AppOpsManager.MODE_ALLOWED)
+ }
+
private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
lateinit var isAllowedState: State<Boolean?>
composeTestRule.setContent {
@@ -236,6 +252,7 @@
override val footerResId = R.string.test_app_op_permission_footer
override val appOp = AppOpsManager.OP_MANAGE_MEDIA
override val permission = PERMISSION
+ override var setModeByUid = false
}
private companion object {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ed1a0f3..1356e1d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -96,6 +96,7 @@
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.provider.Settings.SetAllResult;
+import android.provider.UpdatableDeviceConfigServiceReadiness;
import android.provider.settings.validators.SystemSettingsValidators;
import android.provider.settings.validators.Validator;
import android.text.TextUtils;
@@ -416,10 +417,16 @@
startWatchingUserRestrictionChanges();
});
ServiceManager.addService("settings", new SettingsService(this));
- ServiceManager.addService("device_config", new DeviceConfigService(this));
+ addDeviceConfigServiceIfNeeded();
return true;
}
+ private void addDeviceConfigServiceIfNeeded() {
+ if (!UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
+ ServiceManager.addService("device_config", new DeviceConfigService(this));
+ }
+ }
+
@Override
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ce3084c..2eb58b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2870,7 +2870,7 @@
Error message shown when a button should be pressed and held to activate it, usually shown when
the user attempted to tap the button or held it for too short a time. [CHAR LIMIT=32].
-->
- <string name="keyguard_affordance_press_too_short">Press and hold to activate</string>
+ <string name="keyguard_affordance_press_too_short">Touch & hold to open</string>
<!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_cancel">Cancel</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index f974e27..edd150c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -19,6 +19,8 @@
import android.content.Context
import android.view.ViewGroup
import com.android.systemui.R
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -36,27 +38,29 @@
@Inject
constructor(
private val context: Context,
- unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
+ statusBarStateController: StatusBarStateController,
+ unfoldProgressProvider: NaturalRotationUnfoldProgressProvider,
) {
/** Certain views only need to move if they are not currently centered */
var statusViewCentered = false
- private val filterSplitShadeOnly = { !statusViewCentered }
- private val filterNever = { true }
+ private val filterKeyguardAndSplitShadeOnly: () -> Boolean = {
+ statusBarStateController.getState() == KEYGUARD && !statusViewCentered }
+ private val filterKeyguard: () -> Boolean = { statusBarStateController.getState() == KEYGUARD }
private val translateAnimator by lazy {
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.keyguard_status_area, START, filterNever),
+ ViewIdToTranslate(R.id.keyguard_status_area, START, filterKeyguard),
ViewIdToTranslate(
- R.id.lockscreen_clock_view_large, START, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterNever),
+ R.id.lockscreen_clock_view_large, START, filterKeyguardAndSplitShadeOnly),
+ ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterKeyguard),
ViewIdToTranslate(
- R.id.notification_stack_scroller, END, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.start_button, START, filterNever),
- ViewIdToTranslate(R.id.end_button, END, filterNever)),
+ R.id.notification_stack_scroller, END, filterKeyguardAndSplitShadeOnly),
+ ViewIdToTranslate(R.id.start_button, START, filterKeyguard),
+ ViewIdToTranslate(R.id.end_button, END, filterKeyguard)),
progressProvider = unfoldProgressProvider)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e3c58ce..271fc7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -875,7 +875,7 @@
Assert.isMainThread();
if (mWakeOnFingerprintAcquiredStart && acquireInfo == FINGERPRINT_ACQUIRED_START) {
mPowerManager.wakeUp(
- SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
"com.android.systemui.keyguard:FINGERPRINT_ACQUIRED_START");
}
for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 815ac68..e42f051 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -120,6 +120,7 @@
private final Interpolator mLinearOutSlowIn;
private final LockPatternUtils mLockPatternUtils;
private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
private final InteractionJankMonitor mInteractionJankMonitor;
// TODO: these should be migrated out once ready
@@ -141,7 +142,6 @@
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
private final @Background DelayableExecutor mBackgroundExecutor;
- private boolean mIsOrientationChanged = false;
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
@Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
@@ -235,6 +235,7 @@
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
@@ -242,8 +243,9 @@
@NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
mConfig.mSensorIds = sensorIds;
return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, jankMonitor, biometricPromptInteractor,
- credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
+ panelInteractionDetector, userManager, lockPatternUtils, jankMonitor,
+ biometricPromptInteractor, credentialViewModelProvider,
+ new Handler(Looper.getMainLooper()), bgExecutor);
}
}
@@ -331,6 +333,7 @@
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
@@ -346,6 +349,7 @@
mHandler = mainHandler;
mWindowManager = mContext.getSystemService(WindowManager.class);
mWakefulnessLifecycle = wakefulnessLifecycle;
+ mPanelInteractionDetector = panelInteractionDetector;
mTranslationY = getResources()
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
@@ -490,22 +494,6 @@
@Override
public void onOrientationChanged() {
maybeUpdatePositionForUdfps(true /* invalidate */);
- mIsOrientationChanged = true;
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus) {
- //it's a workaround to avoid closing BP incorrectly
- //BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true)
- if (mIsOrientationChanged) {
- mIsOrientationChanged = false;
- return;
- }
- Log.v(TAG, "Lost window focus, dismissing the dialog");
- animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
- }
}
@Override
@@ -513,6 +501,8 @@
super.onAttachedToWindow();
mWakefulnessLifecycle.addObserver(this);
+ mPanelInteractionDetector.enable(
+ () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
mBiometricScrollView.addView(mBiometricView);
@@ -666,11 +656,6 @@
mBiometricView.restoreState(savedState);
}
- if (savedState != null) {
- mIsOrientationChanged = savedState.getBoolean(
- AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED);
- }
-
wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
}
@@ -689,6 +674,7 @@
@Override
public void dismissWithoutCallback(boolean animate) {
+ mPanelInteractionDetector.disable();
if (animate) {
animateAway(false /* sendReason */, 0 /* reason */);
} else {
@@ -699,6 +685,7 @@
@Override
public void dismissFromSystemServer() {
+ mPanelInteractionDetector.disable();
animateAway(false /* sendReason */, 0 /* reason */);
}
@@ -761,8 +748,6 @@
mBiometricView != null && mCredentialView == null);
outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
- outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, mIsOrientationChanged);
-
if (mBiometricView != null) {
mBiometricView.onSaveState(outState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a0f3ecb0..dad6ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -164,6 +164,7 @@
@NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
private boolean mAllFingerprintAuthenticatorsRegistered;
@NonNull private final UserManager mUserManager;
@NonNull private final LockPatternUtils mLockPatternUtils;
@@ -721,6 +722,7 @@
Provider<SideFpsController> sidefpsControllerFactory,
@NonNull DisplayManager displayManager,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull UdfpsLogger udfpsLogger,
@@ -767,6 +769,8 @@
});
mWakefulnessLifecycle = wakefulnessLifecycle;
+ mPanelInteractionDetector = panelInteractionDetector;
+
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
int[] faceAuthLocation = context.getResources().getIntArray(
@@ -1149,6 +1153,7 @@
requestId,
multiSensorConfig,
mWakefulnessLifecycle,
+ mPanelInteractionDetector,
mUserManager,
mLockPatternUtils);
@@ -1239,6 +1244,7 @@
String opPackageName, boolean skipIntro, long operationId, long requestId,
@BiometricMultiSensorMode int multiSensorConfig,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils) {
return new AuthContainerView.Builder(mContext)
@@ -1253,8 +1259,9 @@
.setMultiSensorConfig(multiSensorConfig)
.setScaleFactorProvider(() -> getScaleFactor())
.build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, mInteractionJankMonitor,
- mBiometricPromptInteractor, mCredentialViewModelProvider);
+ panelInteractionDetector, userManager, lockPatternUtils,
+ mInteractionJankMonitor, mBiometricPromptInteractor,
+ mCredentialViewModelProvider);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index cd0fc37..51f39b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -48,8 +48,6 @@
String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type";
String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props";
- String KEY_BIOMETRIC_ORIENTATION_CHANGED = "orientation_changed";
-
int SIZE_UNKNOWN = 0;
/**
* Minimal UI, showing only biometric icon.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
new file mode 100644
index 0000000..64211b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -0,0 +1,53 @@
+package com.android.systemui.biometrics
+
+import android.annotation.AnyThread
+import android.annotation.MainThread
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class AuthDialogPanelInteractionDetector
+@Inject
+constructor(
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ @Main private val mainExecutor: Executor,
+) {
+ private var action: Action? = null
+
+ @MainThread
+ fun enable(onPanelInteraction: Runnable) {
+ if (action == null) {
+ action = Action(onPanelInteraction)
+ shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+ } else {
+ Log.e(TAG, "Already enabled")
+ }
+ }
+
+ @MainThread
+ fun disable() {
+ if (action != null) {
+ action = null
+ shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged)
+ }
+ }
+
+ @AnyThread
+ private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
+ mainExecutor.execute {
+ action?.let {
+ if (event.tracking) {
+ Log.v(TAG, "Detected panel interaction, event: $event")
+ it.onPanelInteraction.run()
+ disable()
+ }
+ }
+ }
+}
+
+private data class Action(val onPanelInteraction: Runnable)
+
+private const val TAG = "AuthDialogPanelInteractionDetector"
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 82e5704..805a20a 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -16,15 +16,21 @@
package com.android.systemui.clipboardoverlay;
+import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
+
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
+
+import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -56,6 +62,7 @@
private final DeviceConfigProxy mDeviceConfig;
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
+ private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
private final UiEventLogger mUiEventLogger;
private final FeatureFlags mFeatureFlags;
@@ -66,6 +73,7 @@
public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardOverlayControllerLegacyFactory overlayFactory,
+ ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
UiEventLogger uiEventLogger,
FeatureFlags featureFlags) {
@@ -73,6 +81,7 @@
mDeviceConfig = deviceConfigProxy;
mOverlayProvider = clipboardOverlayControllerProvider;
mOverlayFactory = overlayFactory;
+ mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
mUiEventLogger = uiEventLogger;
mFeatureFlags = featureFlags;
@@ -102,6 +111,15 @@
return;
}
+ if (!isUserSetupComplete()) {
+ // just show a toast, user should not access intents from this state
+ if (shouldShowToast(clipData)) {
+ mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
+ mClipboardToast.showCopiedToast();
+ }
+ return;
+ }
+
boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
mUsingNewOverlay = enabled;
@@ -136,10 +154,26 @@
return clipData.getDescription().getExtras().getBoolean(EXTRA_SUPPRESS_OVERLAY, false);
}
+ boolean shouldShowToast(ClipData clipData) {
+ if (clipData == null) {
+ return false;
+ } else if (clipData.getDescription().getClassificationStatus() == CLASSIFICATION_COMPLETE) {
+ // only show for classification complete if we aren't already showing a toast, to ignore
+ // the duplicate ClipData with classification
+ return !mClipboardToast.isShowing();
+ }
+ return true;
+ }
+
private static boolean isEmulator() {
return SystemProperties.getBoolean("ro.boot.qemu", false);
}
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
interface ClipboardOverlay {
void setClipData(ClipData clipData, String clipSource);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
index 9917507..4b5f876 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
@@ -43,7 +43,9 @@
@UiEvent(doc = "clipboard overlay tapped outside")
CLIPBOARD_OVERLAY_TAP_OUTSIDE(1077),
@UiEvent(doc = "clipboard overlay dismissed, miscellaneous reason")
- CLIPBOARD_OVERLAY_DISMISSED_OTHER(1078);
+ CLIPBOARD_OVERLAY_DISMISSED_OTHER(1078),
+ @UiEvent(doc = "clipboard toast shown")
+ CLIPBOARD_TOAST_SHOWN(1270);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
new file mode 100644
index 0000000..0ed7d27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * Utility class for showing a simple clipboard toast on copy.
+ */
+class ClipboardToast extends Toast.Callback {
+ private final Context mContext;
+ private Toast mCopiedToast;
+
+ @Inject
+ ClipboardToast(Context context) {
+ mContext = context;
+ }
+
+ void showCopiedToast() {
+ if (mCopiedToast != null) {
+ mCopiedToast.cancel();
+ }
+ mCopiedToast = Toast.makeText(mContext,
+ R.string.clipboard_overlay_text_copied, Toast.LENGTH_SHORT);
+ mCopiedToast.show();
+ }
+
+ boolean isShowing() {
+ return mCopiedToast != null;
+ }
+
+ @Override // Toast.Callback
+ public void onToastHidden() {
+ super.onToastHidden();
+ mCopiedToast = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
index 16f4150..c746efd 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
@@ -20,7 +20,7 @@
import android.database.ContentObserver
import android.os.Handler
import android.os.Looper
-import android.provider.Settings
+import com.android.systemui.util.settings.GlobalSettings
/**
* Class to track the availability of [DemoMode]. Use this class to track the availability and
@@ -29,7 +29,10 @@
* This class works by wrapping a content observer for the relevant keys related to DemoMode
* availability and current on/off state, and triggering callbacks.
*/
-abstract class DemoModeAvailabilityTracker(val context: Context) {
+abstract class DemoModeAvailabilityTracker(
+ val context: Context,
+ val globalSettings: GlobalSettings,
+) {
var isInDemoMode = false
var isDemoModeAvailable = false
@@ -41,9 +44,9 @@
fun startTracking() {
val resolver = context.contentResolver
resolver.registerContentObserver(
- Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
+ globalSettings.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
resolver.registerContentObserver(
- Settings.Global.getUriFor(DEMO_MODE_ON), false, onObserver)
+ globalSettings.getUriFor(DEMO_MODE_ON), false, onObserver)
}
fun stopTracking() {
@@ -57,12 +60,11 @@
abstract fun onDemoModeFinished()
private fun checkIsDemoModeAllowed(): Boolean {
- return Settings.Global
- .getInt(context.contentResolver, DEMO_MODE_ALLOWED, 0) != 0
+ return globalSettings.getInt(DEMO_MODE_ALLOWED, 0) != 0
}
private fun checkIsDemoModeOn(): Boolean {
- return Settings.Global.getInt(context.contentResolver, DEMO_MODE_ON, 0) != 0
+ return globalSettings.getInt(DEMO_MODE_ON, 0) != 0
}
private val allowedObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index 000bbe6..84f83f1 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -24,22 +24,28 @@
import android.os.UserHandle
import android.util.Log
import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.demomode.DemoMode.ACTION_DEMO
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.util.Assert
import com.android.systemui.util.settings.GlobalSettings
import java.io.PrintWriter
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
/**
* Handles system broadcasts for [DemoMode]
*
* Injected via [DemoModeModule]
*/
-class DemoModeController constructor(
+class DemoModeController
+constructor(
private val context: Context,
private val dumpManager: DumpManager,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
) : CallbackController<DemoMode>, Dumpable {
// Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
@@ -58,9 +64,7 @@
requestFinishDemoMode()
val m = mutableMapOf<String, MutableList<DemoMode>>()
- DemoMode.COMMANDS.map { command ->
- m.put(command, mutableListOf())
- }
+ DemoMode.COMMANDS.map { command -> m.put(command, mutableListOf()) }
receiverMap = m
}
@@ -71,7 +75,7 @@
initialized = true
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
// Due to DemoModeFragment running in systemui:tuner process, we have to observe for
// content changes to know if the setting turned on or off
@@ -81,8 +85,13 @@
val demoFilter = IntentFilter()
demoFilter.addAction(ACTION_DEMO)
- context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter,
- android.Manifest.permission.DUMP, null, Context.RECEIVER_EXPORTED)
+
+ broadcastDispatcher.registerReceiver(
+ receiver = broadcastReceiver,
+ filter = demoFilter,
+ user = UserHandle.ALL,
+ permission = android.Manifest.permission.DUMP,
+ )
}
override fun addCallback(listener: DemoMode) {
@@ -91,16 +100,15 @@
commands.forEach { command ->
if (!receiverMap.containsKey(command)) {
- throw IllegalStateException("Command ($command) not recognized. " +
- "See DemoMode.java for valid commands")
+ throw IllegalStateException(
+ "Command ($command) not recognized. " + "See DemoMode.java for valid commands"
+ )
}
receiverMap[command]!!.add(listener)
}
- synchronized(this) {
- receivers.add(listener)
- }
+ synchronized(this) { receivers.add(listener) }
if (isInDemoMode) {
listener.onDemoModeStarted()
@@ -109,14 +117,46 @@
override fun removeCallback(listener: DemoMode) {
synchronized(this) {
- listener.demoCommands().forEach { command ->
- receiverMap[command]!!.remove(listener)
- }
+ listener.demoCommands().forEach { command -> receiverMap[command]!!.remove(listener) }
receivers.remove(listener)
}
}
+ /**
+ * Create a [Flow] for the stream of demo mode arguments that come in for the given [command]
+ *
+ * This is equivalent of creating a listener manually and adding an event handler for the given
+ * command, like so:
+ *
+ * ```
+ * class Demoable {
+ * private val demoHandler = object : DemoMode {
+ * override fun demoCommands() = listOf(<command>)
+ *
+ * override fun dispatchDemoCommand(command: String, args: Bundle) {
+ * handleDemoCommand(args)
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param command The top-level demo mode command you want a stream for
+ */
+ fun demoFlowForCommand(command: String): Flow<Bundle> = conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun demoCommands(): List<String> = listOf(command)
+
+ override fun dispatchDemoCommand(command: String, args: Bundle) {
+ trySend(args)
+ }
+ }
+
+ addCallback(callback)
+ awaitClose { removeCallback(callback) }
+ }
+
private fun setIsDemoModeAllowed(enabled: Boolean) {
// Turn off demo mode if it was on
if (isInDemoMode && !enabled) {
@@ -129,13 +169,9 @@
Assert.isMainThread()
val copy: List<DemoModeCommandReceiver>
- synchronized(this) {
- copy = receivers.toList()
- }
+ synchronized(this) { copy = receivers.toList() }
- copy.forEach { r ->
- r.onDemoModeStarted()
- }
+ copy.forEach { r -> r.onDemoModeStarted() }
}
private fun exitDemoMode() {
@@ -143,18 +179,13 @@
Assert.isMainThread()
val copy: List<DemoModeCommandReceiver>
- synchronized(this) {
- copy = receivers.toList()
- }
+ synchronized(this) { copy = receivers.toList() }
- copy.forEach { r ->
- r.onDemoModeFinished()
- }
+ copy.forEach { r -> r.onDemoModeFinished() }
}
fun dispatchDemoCommand(command: String, args: Bundle) {
Assert.isMainThread()
-
if (DEBUG) {
Log.d(TAG, "dispatchDemoCommand: $command, args=$args")
}
@@ -172,9 +203,7 @@
}
// See? demo mode is easy now, you just notify the listeners when their command is called
- receiverMap[command]!!.forEach { receiver ->
- receiver.dispatchDemoCommand(command, args)
- }
+ receiverMap[command]!!.forEach { receiver -> receiver.dispatchDemoCommand(command, args) }
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -183,65 +212,64 @@
pw.println(" isDemoModeAllowed=$isAvailable")
pw.print(" receivers=[")
val copy: List<DemoModeCommandReceiver>
- synchronized(this) {
- copy = receivers.toList()
- }
- copy.forEach { recv ->
- pw.print(" ${recv.javaClass.simpleName}")
- }
+ synchronized(this) { copy = receivers.toList() }
+ copy.forEach { recv -> pw.print(" ${recv.javaClass.simpleName}") }
pw.println(" ]")
pw.println(" receiverMap= [")
receiverMap.keys.forEach { command ->
pw.print(" $command : [")
- val recvs = receiverMap[command]!!.map { receiver ->
- receiver.javaClass.simpleName
- }.joinToString(",")
+ val recvs =
+ receiverMap[command]!!
+ .map { receiver -> receiver.javaClass.simpleName }
+ .joinToString(",")
pw.println("$recvs ]")
}
}
- private val tracker = object : DemoModeAvailabilityTracker(context) {
- override fun onDemoModeAvailabilityChanged() {
- setIsDemoModeAllowed(isDemoModeAvailable)
- }
+ private val tracker =
+ object : DemoModeAvailabilityTracker(context, globalSettings) {
+ override fun onDemoModeAvailabilityChanged() {
+ setIsDemoModeAllowed(isDemoModeAvailable)
+ }
- override fun onDemoModeStarted() {
- if (this@DemoModeController.isInDemoMode != isInDemoMode) {
- enterDemoMode()
+ override fun onDemoModeStarted() {
+ if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+ enterDemoMode()
+ }
+ }
+
+ override fun onDemoModeFinished() {
+ if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+ exitDemoMode()
+ }
}
}
- override fun onDemoModeFinished() {
- if (this@DemoModeController.isInDemoMode != isInDemoMode) {
- exitDemoMode()
+ private val broadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (DEBUG) {
+ Log.v(TAG, "onReceive: $intent")
+ }
+
+ val action = intent.action
+ if (!ACTION_DEMO.equals(action)) {
+ return
+ }
+
+ val bundle = intent.extras ?: return
+ val command = bundle.getString("command", "").trim().lowercase()
+ if (command.isEmpty()) {
+ return
+ }
+
+ try {
+ dispatchDemoCommand(command, bundle)
+ } catch (t: Throwable) {
+ Log.w(TAG, "Error running demo command, intent=$intent $t")
+ }
}
}
- }
-
- private val broadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (DEBUG) {
- Log.v(TAG, "onReceive: $intent")
- }
-
- val action = intent.action
- if (!ACTION_DEMO.equals(action)) {
- return
- }
-
- val bundle = intent.extras ?: return
- val command = bundle.getString("command", "").trim().toLowerCase()
- if (command.length == 0) {
- return
- }
-
- try {
- dispatchDemoCommand(command, bundle)
- } catch (t: Throwable) {
- Log.w(TAG, "Error running demo command, intent=$intent $t")
- }
- }
- }
fun requestSetDemoModeAllowed(allowed: Boolean) {
setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
@@ -258,10 +286,12 @@
private fun setGlobal(key: String, value: Int) {
globalSettings.putInt(key, value)
}
+
+ companion object {
+ const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
+ const val DEMO_MODE_ON = "sysui_tuner_demo_on"
+ }
}
private const val TAG = "DemoModeController"
-private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
-private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
-
private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
index de9affb..b84fa5a 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
@@ -18,6 +18,7 @@
import android.content.Context;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
@@ -37,8 +38,14 @@
static DemoModeController provideDemoModeController(
Context context,
DumpManager dumpManager,
- GlobalSettings globalSettings) {
- DemoModeController dmc = new DemoModeController(context, dumpManager, globalSettings);
+ GlobalSettings globalSettings,
+ BroadcastDispatcher broadcastDispatcher
+ ) {
+ DemoModeController dmc = new DemoModeController(
+ context,
+ dumpManager,
+ globalSettings,
+ broadcastDispatcher);
dmc.initialize();
return /*run*/dmc;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 5d21349..5b90ef2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -16,7 +16,14 @@
package com.android.systemui.doze;
+import static android.os.PowerManager.WAKE_REASON_BIOMETRIC;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_LIFT;
+import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManager.WAKE_REASON_TAP;
+
import android.annotation.IntDef;
+import android.os.PowerManager;
import android.util.TimeUtils;
import androidx.annotation.NonNull;
@@ -511,6 +518,25 @@
}
}
+ /**
+ * Converts {@link Reason} to {@link PowerManager.WakeReason}.
+ */
+ public static @PowerManager.WakeReason int getPowerManagerWakeReason(@Reason int wakeReason) {
+ switch (wakeReason) {
+ case REASON_SENSOR_DOUBLE_TAP:
+ case REASON_SENSOR_TAP:
+ return WAKE_REASON_TAP;
+ case REASON_SENSOR_PICKUP:
+ return WAKE_REASON_LIFT;
+ case REASON_SENSOR_UDFPS_LONG_PRESS:
+ return WAKE_REASON_BIOMETRIC;
+ case PULSE_REASON_DOCKING:
+ return WAKE_REASON_PLUGGED_IN;
+ default:
+ return WAKE_REASON_GESTURE;
+ }
+ }
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index f8bd1e7..ba38ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -116,8 +116,8 @@
@Override
public void requestWakeUp(@DozeLog.Reason int reason) {
- PowerManager pm = getSystemService(PowerManager.class);
- pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ final PowerManager pm = getSystemService(PowerManager.class);
+ pm.wakeUp(SystemClock.uptimeMillis(), DozeLog.getPowerManagerWakeReason(reason),
"com.android.systemui:NODOZE " + DozeLog.reasonToString(reason));
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
deleted file mode 100644
index ab4632b..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.policy.CallbackController
-import javax.inject.Inject
-
-/** Dream-related callback information */
-@SysUISingleton
-class DreamCallbackController @Inject constructor() :
- CallbackController<DreamCallbackController.DreamCallback> {
-
- private val callbacks = mutableSetOf<DreamCallbackController.DreamCallback>()
-
- override fun addCallback(callback: DreamCallbackController.DreamCallback) {
- callbacks.add(callback)
- }
-
- override fun removeCallback(callback: DreamCallbackController.DreamCallback) {
- callbacks.remove(callback)
- }
-
- fun onWakeUp() {
- callbacks.forEach { it.onWakeUp() }
- }
-
- interface DreamCallback {
- fun onWakeUp()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index abe9355..c882f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -101,13 +101,11 @@
transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
}
.collect { px ->
- setElementsTranslationYAtPosition(
- px,
- ComplicationLayoutParams.POSITION_TOP
- )
- setElementsTranslationYAtPosition(
- px,
- ComplicationLayoutParams.POSITION_BOTTOM
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationYAtPosition(px, position)
+ },
+ POSITION_TOP or POSITION_BOTTOM
)
}
}
@@ -115,15 +113,15 @@
/* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
launch {
transitionViewModel.dreamOverlayAlpha.collect { alpha ->
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = ComplicationLayoutParams.POSITION_TOP,
- fadingOut = true,
- )
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = ComplicationLayoutParams.POSITION_BOTTOM,
- fadingOut = true,
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = position,
+ fadingOut = true,
+ )
+ },
+ POSITION_TOP or POSITION_BOTTOM
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
new file mode 100644
index 0000000..d5ff8f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Dream overlay-related callback information */
+@SysUISingleton
+class DreamOverlayCallbackController @Inject constructor() :
+ CallbackController<DreamOverlayCallbackController.Callback> {
+
+ private val callbacks = mutableSetOf<DreamOverlayCallbackController.Callback>()
+
+ var isDreaming = false
+ private set
+
+ override fun addCallback(callback: DreamOverlayCallbackController.Callback) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: DreamOverlayCallbackController.Callback) {
+ callbacks.remove(callback)
+ }
+
+ fun onWakeUp() {
+ isDreaming = false
+ callbacks.forEach { it.onWakeUp() }
+ }
+
+ fun onStartDream() {
+ isDreaming = true
+ callbacks.forEach { it.onStartDream() }
+ }
+
+ interface Callback {
+ /** Dream overlay has ended */
+ fun onWakeUp()
+
+ /** Dream overlay has started */
+ fun onStartDream()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 1763dd9..fdc115b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -70,7 +70,7 @@
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
- private final DreamCallbackController mDreamCallbackController;
+ private final DreamOverlayCallbackController mDreamOverlayCallbackController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -151,7 +151,7 @@
TouchInsetManager touchInsetManager,
@Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
ComponentName lowLightDreamComponent,
- DreamCallbackController dreamCallbackController) {
+ DreamOverlayCallbackController dreamOverlayCallbackController) {
mContext = context;
mExecutor = executor;
mWindowManager = windowManager;
@@ -160,7 +160,7 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
- mDreamCallbackController = dreamCallbackController;
+ mDreamOverlayCallbackController = dreamOverlayCallbackController;
final ViewModelStore viewModelStore = new ViewModelStore();
final Complication.Host host =
@@ -229,6 +229,7 @@
dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
+ mDreamOverlayCallbackController.onStartDream();
mStarted = true;
});
}
@@ -245,7 +246,7 @@
public void onWakeUp(@NonNull Runnable onCompletedCallback) {
mExecutor.execute(() -> {
if (mDreamOverlayContainerViewController != null) {
- mDreamCallbackController.onWakeUp();
+ mDreamOverlayCallbackController.onWakeUp();
mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
}
});
@@ -255,6 +256,7 @@
* Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
* called from the main executing thread. The window attributes closely mirror those that are
* set by the {@link android.service.dreams.DreamService} on the dream Window.
+ *
* @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting
* into the dream window.
*/
@@ -301,7 +303,11 @@
private void resetCurrentDreamOverlayLocked() {
if (mStarted && mWindow != null) {
- mWindowManager.removeView(mWindow.getDecorView());
+ try {
+ mWindowManager.removeView(mWindow.getDecorView());
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Error removing decor view when resetting overlay", e);
+ }
}
mStateController.setOverlayActive(false);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4268d3e..7b876d0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -330,6 +330,8 @@
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
+ val SHOW_LOWLIGHT_ON_DIRECT_BOOT = unreleasedFlag(1003, "show_lowlight_on_direct_boot")
+
// 1100 - windowing
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 9a0fbbf..a4fd087 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -29,8 +29,7 @@
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
-import com.android.systemui.dreams.DreamCallbackController
-import com.android.systemui.dreams.DreamCallbackController.DreamCallback
+import com.android.systemui.dreams.DreamOverlayCallbackController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -49,7 +48,6 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.merge
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -81,6 +79,9 @@
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Is an activity showing over the keyguard? */
+ val isKeyguardOccluded: Flow<Boolean>
+
/** Observable for the signal that keyguard is about to go away. */
val isKeyguardGoingAway: Flow<Boolean>
@@ -107,6 +108,9 @@
*/
val isDreaming: Flow<Boolean>
+ /** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
+ val isDreamingWithOverlay: Flow<Boolean>
+
/**
* Observable for the amount of doze we are currently in.
*
@@ -179,7 +183,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val authController: AuthController,
- private val dreamCallbackController: DreamCallbackController,
+ private val dreamOverlayCallbackController: DreamOverlayCallbackController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -191,28 +195,55 @@
private val _clockPosition = MutableStateFlow(Position(0, 0))
override val clockPosition = _clockPosition.asStateFlow()
- override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow {
- val callback =
- object : KeyguardStateController.Callback {
- override fun onKeyguardShowingChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isShowing,
- TAG,
- "updated isKeyguardShowing"
- )
- }
+ override val isKeyguardShowing: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isShowing,
+ TAG,
+ "updated isKeyguardShowing"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isShowing,
+ TAG,
+ "initial isKeyguardShowing"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
}
+ .distinctUntilChanged()
- keyguardStateController.addCallback(callback)
- // Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- keyguardStateController.isShowing,
- TAG,
- "initial isKeyguardShowing"
- )
+ override val isKeyguardOccluded: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isOccluded,
+ TAG,
+ "updated isKeyguardOccluded"
+ )
+ }
+ }
- awaitClose { keyguardStateController.removeCallback(callback) }
- }
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isOccluded,
+ TAG,
+ "initial isKeyguardOccluded"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
val callback =
@@ -279,36 +310,45 @@
}
.distinctUntilChanged()
+ override val isDreamingWithOverlay: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DreamOverlayCallbackController.Callback {
+ override fun onStartDream() {
+ trySendWithFailureLogging(true, TAG, "updated isDreamingWithOverlay")
+ }
+ override fun onWakeUp() {
+ trySendWithFailureLogging(false, TAG, "updated isDreamingWithOverlay")
+ }
+ }
+ dreamOverlayCallbackController.addCallback(callback)
+ trySendWithFailureLogging(
+ dreamOverlayCallbackController.isDreaming,
+ TAG,
+ "initial isDreamingWithOverlay",
+ )
+
+ awaitClose { dreamOverlayCallbackController.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+
override val isDreaming: Flow<Boolean> =
- merge(
- conflatedCallbackFlow {
- val callback =
- object : KeyguardUpdateMonitorCallback() {
- override fun onDreamingStateChanged(isDreaming: Boolean) {
- trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
- }
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onDreamingStateChanged(isDreaming: Boolean) {
+ trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
}
- keyguardUpdateMonitor.registerCallback(callback)
- trySendWithFailureLogging(
- keyguardUpdateMonitor.isDreaming,
- TAG,
- "initial isDreaming",
- )
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(
+ keyguardUpdateMonitor.isDreaming,
+ TAG,
+ "initial isDreaming",
+ )
- awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
- },
- conflatedCallbackFlow {
- val callback =
- object : DreamCallback {
- override fun onWakeUp() {
- trySendWithFailureLogging(false, TAG, "updated isDreaming")
- }
- }
- dreamCallbackController.addCallback(callback)
-
- awaitClose { dreamCallbackController.removeCallback(callback) }
- }
- )
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
.distinctUntilChanged()
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index d72d7183b..343c2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -131,6 +131,10 @@
}
override fun startTransition(info: TransitionInfo): UUID? {
+ if (lastStep.from == info.from && lastStep.to == info.to) {
+ Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
+ return null
+ }
if (lastStep.transitionState != TransitionState.FINISHED) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
deleted file mode 100644
index dad166f..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class AodToGoneTransitionInteractor
-@Inject
-constructor(
- @Application private val scope: CoroutineScope,
- private val keyguardInteractor: KeyguardInteractor,
- private val keyguardTransitionRepository: KeyguardTransitionRepository,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
-
- override fun start() {
- scope.launch {
- keyguardInteractor.biometricUnlockState
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
- .collect { pair ->
- val (biometricUnlockState, keyguardState) = pair
- if (
- keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
- ) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.AOD,
- KeyguardState.GONE,
- getAnimator(),
- )
- )
- }
- }
- }
- }
-
- private fun getAnimator(): ValueAnimator {
- return ValueAnimator().apply {
- setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
- }
- }
-
- companion object {
- private const val TRANSITION_DURATION_MS = 500L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index f3d2905..c2d139c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -30,33 +31,33 @@
import kotlinx.coroutines.launch
@SysUISingleton
-class AodLockscreenTransitionInteractor
+class FromAodTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromAodTransitionInteractor::class.simpleName!!) {
override fun start() {
- listenForTransitionToAodFromLockscreen()
- listenForTransitionToLockscreenFromDozeStates()
+ listenForAodToLockscreen()
+ listenForAodToGone()
}
- private fun listenForTransitionToAodFromLockscreen() {
+ private fun listenForAodToLockscreen() {
scope.launch {
keyguardInteractor
- .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+ .dozeTransitionTo(DozeStateModel.FINISH)
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (dozeToAod, lastStartedStep) = pair
- if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ if (lastStartedStep.to == KeyguardState.AOD) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
- KeyguardState.LOCKSCREEN,
KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
getAnimator(),
)
)
@@ -65,20 +66,20 @@
}
}
- private fun listenForTransitionToLockscreenFromDozeStates() {
- val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+ private fun listenForAodToGone() {
scope.launch {
- keyguardInteractor
- .dozeTransitionTo(DozeStateModel.FINISH)
- .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
- val (dozeToAod, lastStartedStep) = pair
- if (canGoToLockscreen.contains(lastStartedStep.to)) {
+ val (biometricUnlockState, keyguardState) = pair
+ if (
+ keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
- lastStartedStep.to,
- KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ KeyguardState.GONE,
getAnimator(),
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
index 056c44d..0e9c447 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
@@ -23,16 +23,18 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
-class BouncerToGoneTransitionInteractor
+class FromBouncerTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
@@ -40,15 +42,54 @@
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromBouncerTransitionInteractor::class.simpleName!!) {
private var transitionId: UUID? = null
override fun start() {
- listenForKeyguardGoingAway()
+ listenForBouncerToGone()
+ listenForBouncerToLockscreenOrAod()
}
- private fun listenForKeyguardGoingAway() {
+ private fun listenForBouncerToLockscreenOrAod() {
+ scope.launch {
+ keyguardInteractor.isBouncerShowing
+ .sample(
+ combine(
+ keyguardInteractor.wakefulnessModel,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+ if (
+ !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
+ ) {
+ val to =
+ if (
+ wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
+ wakefulnessState.state == WakefulnessState.ASLEEP
+ ) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.BOUNCER,
+ to = to,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForBouncerToGone() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 95d9602..fd2d271 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,36 +21,46 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
-class LockscreenGoneTransitionInteractor
+class FromDozingTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(FromDozingTransitionInteractor::class.simpleName!!) {
override fun start() {
+ listenForDozingToLockscreen()
+ }
+
+ private fun listenForDozingToLockscreen() {
scope.launch {
- keyguardInteractor.isKeyguardGoingAway
+ keyguardInteractor.dozeTransitionModel
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
- val (isKeyguardGoingAway, lastStartedStep) = pair
- if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ val (dozeTransitionModel, lastStartedTransition) = pair
+ if (
+ isDozeOff(dozeTransitionModel.to) &&
+ lastStartedTransition.to == KeyguardState.DOZING
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
+ KeyguardState.DOZING,
KeyguardState.LOCKSCREEN,
- KeyguardState.GONE,
getAnimator(),
)
)
@@ -59,14 +69,14 @@
}
}
- private fun getAnimator(): ValueAnimator {
+ private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
+ setDuration(duration.inWholeMilliseconds)
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 10L
+ private val DEFAULT_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 188930c..3b09ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -36,75 +36,40 @@
import kotlinx.coroutines.launch
@SysUISingleton
-class DreamingTransitionInteractor
+class FromDreamingTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
-
- private val canDreamFrom =
- setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
+) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
override fun start() {
- listenForEntryToDreaming()
listenForDreamingToLockscreen()
+ listenForDreamingToOccluded()
listenForDreamingToGone()
listenForDreamingToDozing()
}
- private fun listenForEntryToDreaming() {
- scope.launch {
- keyguardInteractor.isDreaming
- .sample(
- combine(
- keyguardInteractor.dozeTransitionModel,
- keyguardTransitionInteractor.finishedKeyguardState,
- ::Pair
- ),
- ::toTriple
- )
- .collect { triple ->
- val (isDreaming, dozeTransitionModel, keyguardState) = triple
- // Dozing/AOD and dreaming have overlapping events. If the state remains in
- // FINISH, it means that doze mode is not running and DREAMING is ok to
- // commence.
- if (
- isDozeOff(dozeTransitionModel.to) &&
- isDreaming &&
- canDreamFrom.contains(keyguardState)
- ) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- keyguardState,
- KeyguardState.DREAMING,
- getAnimator(),
- )
- )
- }
- }
- }
- }
-
private fun listenForDreamingToLockscreen() {
scope.launch {
- keyguardInteractor.isDreaming
+ // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
+ // otherwise would have gone through OCCLUDED first
+ keyguardInteractor.isDreamingWithOverlay
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ::Pair,
+ ::Pair
),
::toTriple
)
.collect { triple ->
val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
if (
- isDozeOff(dozeTransitionModel.to) &&
- !isDreaming &&
+ !isDreaming &&
+ isDozeOff(dozeTransitionModel.to) &&
lastStartedTransition.to == KeyguardState.DREAMING
) {
keyguardTransitionRepository.startTransition(
@@ -120,6 +85,42 @@
}
}
+ private fun listenForDreamingToOccluded() {
+ scope.launch {
+ keyguardInteractor.isDreaming
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair,
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isDreaming, isOccluded, lastStartedTransition) = triple
+ if (
+ isOccluded &&
+ !isDreaming &&
+ (lastStartedTransition.to == KeyguardState.DREAMING ||
+ lastStartedTransition.to == KeyguardState.LOCKSCREEN)
+ ) {
+ // At the moment, checking for LOCKSCREEN state above provides a corrective
+ // action. There's no great signal to determine when the dream is ending
+ // and a transition to OCCLUDED is beginning directly. For now, the solution
+ // is DREAMING->LOCKSCREEN->OCCLUDED
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ lastStartedTransition.to,
+ KeyguardState.OCCLUDED,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun listenForDreamingToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index a50e759..553fafe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -30,19 +30,44 @@
import kotlinx.coroutines.launch
@SysUISingleton
-class GoneAodTransitionInteractor
+class FromGoneTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
override fun start() {
+ listenForGoneToAod()
+ listenForGoneToDreaming()
+ }
+
+ private fun listenForGoneToDreaming() {
+ scope.launch {
+ keyguardInteractor.isAbleToDream
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+ .collect { pair ->
+ val (isAbleToDream, keyguardState) = pair
+ if (isAbleToDream && keyguardState == KeyguardState.GONE) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.GONE,
+ KeyguardState.DREAMING,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForGoneToAod() {
scope.launch {
keyguardInteractor.wakefulnessModel
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
val (wakefulnessState, keyguardState) = pair
if (
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
new file mode 100644
index 0000000..326acc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class FromLockscreenTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val shadeRepository: ShadeRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
+
+ private var transitionId: UUID? = null
+
+ override fun start() {
+ listenForLockscreenToGone()
+ listenForLockscreenToOccluded()
+ listenForLockscreenToAod()
+ listenForLockscreenToBouncer()
+ listenForLockscreenToDreaming()
+ listenForLockscreenToBouncerDragging()
+ }
+
+ private fun listenForLockscreenToDreaming() {
+ scope.launch {
+ keyguardInteractor.isAbleToDream
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isAbleToDream, lastStartedTransition) = pair
+ if (isAbleToDream && lastStartedTransition.to == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.DREAMING,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToBouncer() {
+ scope.launch {
+ keyguardInteractor.isBouncerShowing
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isBouncerShowing, lastStartedTransitionStep) = pair
+ if (
+ isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.BOUNCER,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+ private fun listenForLockscreenToBouncerDragging() {
+ scope.launch {
+ shadeRepository.shadeModel
+ .sample(
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState,
+ keyguardInteractor.statusBarState,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (shadeModel, keyguardState, statusBarState) = triple
+
+ val id = transitionId
+ if (id != null) {
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED
+ keyguardTransitionRepository.updateTransition(
+ id,
+ 1f - shadeModel.expansionAmount,
+ if (
+ shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
+ ) {
+ transitionId = null
+ TransitionState.FINISHED
+ } else {
+ TransitionState.RUNNING
+ }
+ )
+ } else {
+ // TODO (b/251849525): Remove statusbarstate check when that state is
+ // integrated into KeyguardTransitionRepository
+ if (
+ keyguardState == KeyguardState.LOCKSCREEN &&
+ shadeModel.isUserDragging &&
+ statusBarState == KEYGUARD
+ ) {
+ transitionId =
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.BOUNCER,
+ animator = null,
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToGone() {
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isKeyguardGoingAway, lastStartedStep) = pair
+ if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToOccluded() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState,
+ keyguardInteractor.isDreaming,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { triple ->
+ val (isOccluded, keyguardState, isDreaming) = triple
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (isOccluded && !isDreaming) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ keyguardState,
+ KeyguardState.OCCLUDED,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToAod() {
+ scope.launch {
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (dozeToAod, lastStartedStep) = pair
+ if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index f3d2905..937e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -21,42 +21,43 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@SysUISingleton
-class AodLockscreenTransitionInteractor
+class FromOccludedTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromOccludedTransitionInteractor::class.simpleName!!) {
override fun start() {
- listenForTransitionToAodFromLockscreen()
- listenForTransitionToLockscreenFromDozeStates()
+ listenForOccludedToLockscreen()
+ listenForOccludedToDreaming()
}
- private fun listenForTransitionToAodFromLockscreen() {
+ private fun listenForOccludedToDreaming() {
scope.launch {
- keyguardInteractor
- .dozeTransitionTo(DozeStateModel.DOZE_AOD)
- .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ keyguardInteractor.isAbleToDream
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
.collect { pair ->
- val (dozeToAod, lastStartedStep) = pair
- if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ val (isAbleToDream, keyguardState) = pair
+ if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
- KeyguardState.LOCKSCREEN,
- KeyguardState.AOD,
+ KeyguardState.OCCLUDED,
+ KeyguardState.DREAMING,
getAnimator(),
)
)
@@ -65,19 +66,19 @@
}
}
- private fun listenForTransitionToLockscreenFromDozeStates() {
- val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+ private fun listenForOccludedToLockscreen() {
scope.launch {
- keyguardInteractor
- .dozeTransitionTo(DozeStateModel.FINISH)
+ keyguardInteractor.isKeyguardOccluded
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
- val (dozeToAod, lastStartedStep) = pair
- if (canGoToLockscreen.contains(lastStartedStep.to)) {
+ val (isOccluded, lastStartedKeyguardState) = pair
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
- lastStartedStep.to,
+ KeyguardState.OCCLUDED,
KeyguardState.LOCKSCREEN,
getAnimator(),
)
@@ -87,14 +88,15 @@
}
}
- private fun getAnimator(): ValueAnimator {
+ private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
+ setDuration(duration.inWholeMilliseconds)
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 500L
+ private val DEFAULT_DURATION = 500.milliseconds
+ val TO_LOCKSCREEN_DURATION = 1183.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6912e1d..402c179 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -22,12 +22,16 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -52,8 +56,27 @@
* but not vice-versa.
*/
val isDreaming: Flow<Boolean> = repository.isDreaming
+ /** Whether the system is dreaming with an overlay active */
+ val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+
+ /**
+ * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
+ * that doze mode is not running and DREAMING is ok to commence.
+ */
+ val isAbleToDream: Flow<Boolean> =
+ merge(isDreaming, isDreamingWithOverlay)
+ .sample(
+ dozeTransitionModel,
+ { isDreaming, dozeTransitionModel ->
+ isDreaming && isDozeOff(dozeTransitionModel.to)
+ }
+ )
+ .distinctUntilChanged()
+
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the keyguard is occluded (covered by an activity). */
+ val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Whether the bouncer is showing or not. */
@@ -74,8 +97,8 @@
/** The approximate location on the screen of the face unlock sensor, if one is available. */
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
- fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
- return dozeTransitionModel.filter { it.to == state }
+ fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
+ return dozeTransitionModel.filter { states.contains(it.to) }
}
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index bb8b79a..fbed446 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -37,13 +37,13 @@
// exhaustive
val ret =
when (it) {
- is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
- is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
- is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
- is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
- is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
- is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
- is DreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromAodTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromDreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 6e25200..a59c407 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -89,6 +89,7 @@
KeyguardState.BOUNCER -> true
KeyguardState.LOCKSCREEN -> true
KeyguardState.GONE -> true
+ KeyguardState.OCCLUDED -> true
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
deleted file mode 100644
index 5cb7d70..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.WakefulnessState
-import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.util.kotlin.sample
-import java.util.UUID
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class LockscreenBouncerTransitionInteractor
-@Inject
-constructor(
- @Application private val scope: CoroutineScope,
- private val keyguardInteractor: KeyguardInteractor,
- private val shadeRepository: ShadeRepository,
- private val keyguardTransitionRepository: KeyguardTransitionRepository,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
-
- private var transitionId: UUID? = null
-
- override fun start() {
- listenForDraggingUpToBouncer()
- listenForBouncer()
- }
-
- private fun listenForBouncer() {
- scope.launch {
- keyguardInteractor.isBouncerShowing
- .sample(
- combine(
- keyguardInteractor.wakefulnessModel,
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
- )
- .collect { triple ->
- val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
- if (
- !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
- ) {
- val to =
- if (
- wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
- wakefulnessState.state == WakefulnessState.ASLEEP
- ) {
- KeyguardState.AOD
- } else {
- KeyguardState.LOCKSCREEN
- }
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- ownerName = name,
- from = KeyguardState.BOUNCER,
- to = to,
- animator = getAnimator(),
- )
- )
- } else if (
- isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
- ) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- ownerName = name,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.BOUNCER,
- animator = getAnimator(),
- )
- )
- }
- Unit
- }
- }
- }
-
- /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
- private fun listenForDraggingUpToBouncer() {
- scope.launch {
- shadeRepository.shadeModel
- .sample(
- combine(
- keyguardTransitionInteractor.finishedKeyguardState,
- keyguardInteractor.statusBarState,
- ::Pair
- ),
- ::toTriple
- )
- .collect { triple ->
- val (shadeModel, keyguardState, statusBarState) = triple
-
- val id = transitionId
- if (id != null) {
- // An existing `id` means a transition is started, and calls to
- // `updateTransition` will control it until FINISHED
- keyguardTransitionRepository.updateTransition(
- id,
- 1f - shadeModel.expansionAmount,
- if (
- shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
- ) {
- transitionId = null
- TransitionState.FINISHED
- } else {
- TransitionState.RUNNING
- }
- )
- } else {
- // TODO (b/251849525): Remove statusbarstate check when that state is
- // integrated into KeyguardTransitionRepository
- if (
- keyguardState == KeyguardState.LOCKSCREEN &&
- shadeModel.isUserDragging &&
- statusBarState == KEYGUARD
- ) {
- transitionId =
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- ownerName = name,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.BOUNCER,
- animator = null,
- )
- )
- }
- }
- }
- }
- }
-
- private fun getAnimator(): ValueAnimator {
- return ValueAnimator().apply {
- setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
- }
- }
-
- companion object {
- private const val TRANSITION_DURATION_MS = 300L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 5f63ae7..81fa233 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -32,25 +32,25 @@
@Binds
@IntoSet
- abstract fun lockscreenBouncer(
- impl: LockscreenBouncerTransitionInteractor
- ): TransitionInteractor
+ abstract fun fromBouncer(impl: FromBouncerTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
- abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+ abstract fun fromLockscreen(impl: FromLockscreenTransitionInteractor): TransitionInteractor
- @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun fromAod(impl: FromAodTransitionInteractor): TransitionInteractor
- @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun fromGone(impl: FromGoneTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
- abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
+ abstract fun fromDreaming(impl: FromDreamingTransitionInteractor): TransitionInteractor
@Binds
@IntoSet
- abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
+ abstract fun fromOccluded(impl: FromOccludedTransitionInteractor): TransitionInteractor
- @Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
+ @Binds
+ @IntoSet
+ abstract fun fromDozing(impl: FromDozingTransitionInteractor): TransitionInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 08ad3d5..4d24c14 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -31,4 +31,6 @@
abstract fun start()
fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
+
+ fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index dd908c4..c757986 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -57,4 +57,8 @@
* with SWIPE security method or face unlock without bypass.
*/
GONE,
+ /*
+ * An activity is displaying over the keyguard.
+ */
+ OCCLUDED,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index ae8edfe..b19795c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -18,6 +18,7 @@
import android.annotation.SuppressLint
import android.graphics.drawable.Animatable2
+import android.os.VibrationEffect
import android.util.Size
import android.util.TypedValue
import android.view.MotionEvent
@@ -43,8 +44,11 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.kotlin.pairwise
import kotlin.math.pow
import kotlin.math.sqrt
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
@@ -93,6 +97,7 @@
view: ViewGroup,
viewModel: KeyguardBottomAreaViewModel,
falsingManager: FalsingManager?,
+ vibratorHelper: VibratorHelper?,
messageDisplayer: (Int) -> Unit,
): Binding {
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
@@ -118,22 +123,48 @@
viewModel = buttonModel,
falsingManager = falsingManager,
messageDisplayer = messageDisplayer,
+ vibratorHelper = vibratorHelper,
)
}
}
launch {
+ viewModel.startButton
+ .map { it.isActivated }
+ .pairwise()
+ .collect { (prev, next) ->
+ when {
+ !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
+ prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
+ }
+ }
+ }
+
+ launch {
viewModel.endButton.collect { buttonModel ->
updateButton(
view = endButton,
viewModel = buttonModel,
falsingManager = falsingManager,
messageDisplayer = messageDisplayer,
+ vibratorHelper = vibratorHelper,
)
}
}
launch {
+ viewModel.endButton
+ .map { it.isActivated }
+ .pairwise()
+ .collect { (prev, next) ->
+ when {
+ !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
+ prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
+ }
+ }
+ }
+
+ launch {
viewModel.isOverlayContainerVisible.collect { isVisible ->
overlayContainer.visibility =
if (isVisible) {
@@ -239,6 +270,7 @@
viewModel: KeyguardQuickAffordanceViewModel,
falsingManager: FalsingManager?,
messageDisplayer: (Int) -> Unit,
+ vibratorHelper: VibratorHelper?,
) {
if (!viewModel.isVisible) {
view.isVisible = false
@@ -312,7 +344,9 @@
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
if (viewModel.useLongPress) {
- view.setOnTouchListener(OnTouchListener(view, viewModel, messageDisplayer))
+ view.setOnTouchListener(
+ OnTouchListener(view, viewModel, messageDisplayer, vibratorHelper)
+ )
} else {
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
}
@@ -328,6 +362,7 @@
private val view: View,
private val viewModel: KeyguardQuickAffordanceViewModel,
private val messageDisplayer: (Int) -> Unit,
+ private val vibratorHelper: VibratorHelper?,
) : View.OnTouchListener {
private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
@@ -376,25 +411,38 @@
true
}
MotionEvent.ACTION_UP -> {
- if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
- messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
- val shakeAnimator =
- ObjectAnimator.ofFloat(
- view,
- "translationX",
- 0f,
- view.context.resources
- .getDimensionPixelSize(
- R.dimen.keyguard_affordance_shake_amplitude
+ cancel(
+ onAnimationEnd =
+ if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
+ Runnable {
+ messageDisplayer.invoke(
+ R.string.keyguard_affordance_press_too_short
)
- .toFloat(),
- 0f,
- )
- shakeAnimator.duration = 300
- shakeAnimator.interpolator = CycleInterpolator(5f)
- shakeAnimator.start()
- }
- cancel()
+ val amplitude =
+ view.context.resources
+ .getDimensionPixelSize(
+ R.dimen.keyguard_affordance_shake_amplitude
+ )
+ .toFloat()
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ -amplitude / 2,
+ amplitude / 2,
+ )
+ shakeAnimator.duration =
+ ShakeAnimationDuration.inWholeMilliseconds
+ shakeAnimator.interpolator =
+ CycleInterpolator(ShakeAnimationCycles)
+ shakeAnimator.start()
+
+ vibratorHelper?.vibrate(Vibrations.Shake)
+ }
+ } else {
+ null
+ }
+ )
true
}
MotionEvent.ACTION_CANCEL -> {
@@ -405,11 +453,11 @@
}
}
- private fun cancel() {
+ private fun cancel(onAnimationEnd: Runnable? = null) {
downTimestamp = 0L
longPressAnimator?.cancel()
longPressAnimator = null
- view.animate().scaleX(1f).scaleY(1f)
+ view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
}
companion object {
@@ -461,4 +509,58 @@
val indicationTextSizePx: Int,
val buttonSizePx: Size,
)
+
+ private val ShakeAnimationDuration = 300.milliseconds
+ private val ShakeAnimationCycles = 5f
+
+ object Vibrations {
+
+ private const val SmallVibrationScale = 0.3f
+ private const val BigVibrationScale = 0.6f
+
+ val Shake =
+ VibrationEffect.startComposition()
+ .apply {
+ val vibrationDelayMs =
+ (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
+ .toInt()
+ val vibrationCount = ShakeAnimationCycles.toInt() * 2
+ repeat(vibrationCount) {
+ addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ SmallVibrationScale,
+ vibrationDelayMs,
+ )
+ }
+ }
+ .compose()
+
+ val Activated =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ BigVibrationScale,
+ 0,
+ )
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+ 0.1f,
+ 0,
+ )
+ .compose()
+
+ val Deactivated =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ BigVibrationScale,
+ 0,
+ )
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
+ 0.1f,
+ 0,
+ )
+ .compose()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 402fac1..e164f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -19,7 +19,7 @@
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index a6447a5..5716a1d72 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -448,6 +448,10 @@
// Any cleanup needed when the service is being destroyed.
void onDestroy() {
+ if (mSaveInBgTask != null) {
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
+ }
removeWindow();
releaseMediaPlayer();
releaseContext();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index ba779c6..639172f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -19,6 +19,9 @@
import android.content.Context
import android.view.ViewGroup
import com.android.systemui.R
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -30,17 +33,24 @@
@SysUIUnfoldScope
class NotificationPanelUnfoldAnimationController
@Inject
-constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) {
+constructor(
+ private val context: Context,
+ statusBarStateController: StatusBarStateController,
+ progressProvider: NaturalRotationUnfoldProgressProvider,
+) {
+
+ private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
+ statusBarStateController.getState() == SHADE_LOCKED }
private val translateAnimator by lazy {
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.quick_settings_panel, START),
- ViewIdToTranslate(R.id.notification_stack_scroller, END),
- ViewIdToTranslate(R.id.rightLayout, END),
- ViewIdToTranslate(R.id.clock, START),
- ViewIdToTranslate(R.id.date, START)),
+ ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
+ ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+ ViewIdToTranslate(R.id.rightLayout, END, filterShade),
+ ViewIdToTranslate(R.id.clock, START, filterShade),
+ ViewIdToTranslate(R.id.date, START, filterShade)),
progressProvider = progressProvider)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 31543df..3e9d89a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1374,8 +1374,8 @@
mFalsingManager,
mLockIconViewController,
stringResourceId ->
- mKeyguardIndicationController.showTransientIndication(stringResourceId)
- );
+ mKeyguardIndicationController.showTransientIndication(stringResourceId),
+ mVibratorHelper);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 3a011c5..64b6e61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -19,6 +19,7 @@
import android.app.StatusBarManager;
import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
+import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
import android.view.GestureDetector;
@@ -242,7 +243,9 @@
() -> mService.wakeUpIfDozing(
SystemClock.uptimeMillis(),
mView,
- "LOCK_ICON_TOUCH"));
+ "LOCK_ICON_TOUCH",
+ PowerManager.WAKE_REASON_GESTURE)
+ );
// In case we start outside of the view bounds (below the status bar), we need to
// dispatch
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index bf622c9..db70065 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade
import android.hardware.display.AmbientDisplayConfiguration
+import android.os.PowerManager
import android.os.SystemClock
import android.os.UserHandle
import android.provider.Settings
@@ -89,7 +90,8 @@
centralSurfaces.wakeUpIfDozing(
SystemClock.uptimeMillis(),
notificationShadeWindowView,
- "PULSING_SINGLE_TAP"
+ "PULSING_SINGLE_TAP",
+ PowerManager.WAKE_REASON_TAP
)
}
return true
@@ -114,7 +116,9 @@
centralSurfaces.wakeUpIfDozing(
SystemClock.uptimeMillis(),
notificationShadeWindowView,
- "PULSING_DOUBLE_TAP")
+ "PULSING_DOUBLE_TAP",
+ PowerManager.WAKE_REASON_TAP
+ )
return true
}
return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index b8302d7..905cc3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -5,6 +5,7 @@
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
+import android.os.PowerManager
import android.os.SystemClock
import android.util.IndentingPrintWriter
import android.util.MathUtils
@@ -272,7 +273,12 @@
// Bind the click listener of the shelf to go to the full shade
notificationShelfController.setOnClickListener {
if (statusBarStateController.state == StatusBarState.KEYGUARD) {
- centralSurfaces.wakeUpIfDozing(SystemClock.uptimeMillis(), it, "SHADE_CLICK")
+ centralSurfaces.wakeUpIfDozing(
+ SystemClock.uptimeMillis(),
+ it,
+ "SHADE_CLICK",
+ PowerManager.WAKE_REASON_GESTURE,
+ )
goToLockedShade(it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 3516037..8f9365c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -64,6 +65,8 @@
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -71,8 +74,6 @@
import java.util.Optional;
import java.util.function.Consumer;
-import dagger.Lazy;
-
/**
* Class for handling remote input state over a set of notifications. This class handles things
* like keeping notifications temporarily that were cancelled as a response to a remote input
@@ -120,7 +121,8 @@
View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) {
mCentralSurfacesOptionalLazy.get().ifPresent(
centralSurfaces -> centralSurfaces.wakeUpIfDozing(
- SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK"));
+ SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK",
+ PowerManager.WAKE_REASON_GESTURE));
final NotificationEntry entry = getNotificationForParent(view.getParent());
mLogger.logInitialClick(entry, pendingIntent);
@@ -464,9 +466,6 @@
riv.getController().setRemoteInputs(inputs);
riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null;
- if (parent != null) {
- riv.setDefocusTargetHeight(parent.getHeight());
- }
riv.focusAnimated(parent);
if (userMessageContent != null) {
riv.setEditTextContent(userMessageContent);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index c630feb..976924a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -22,7 +22,6 @@
import android.content.Context
import android.content.res.Configuration
import android.os.PowerManager
-import android.os.PowerManager.WAKE_REASON_GESTURE
import android.os.SystemClock
import android.util.IndentingPrintWriter
import android.view.MotionEvent
@@ -249,7 +248,7 @@
}
if (statusBarStateController.isDozing) {
wakeUpCoordinator.willWakeUp = true
- mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
+ mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"com.android.systemui:PULSEDRAG")
}
lockscreenShadeTransitionController.goToLockedShade(startingChild,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index b9074f0..6e74542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -1301,7 +1301,7 @@
}
}
String wifi = args.getString("wifi");
- if (wifi != null) {
+ if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
boolean show = wifi.equals("show");
String level = args.getString("level");
if (level != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index c3ce593..705cf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification;
import android.app.Notification;
+import android.os.PowerManager;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -70,7 +71,8 @@
}
mCentralSurfacesOptional.ifPresent(centralSurfaces -> centralSurfaces.wakeUpIfDozing(
- SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
+ SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK",
+ PowerManager.WAKE_REASON_GESTURE));
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
final NotificationEntry entry = row.getEntry();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index d240d5a..9a8c5d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -420,7 +420,7 @@
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
mLogger.i("bio wakelock: Authenticated, waking up...");
- mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
"android.policy:BIOMETRIC");
}
Trace.beginSection("release wake-and-unlock");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index c7c6441..cf2f7742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.view.KeyEvent;
@@ -205,7 +206,10 @@
@Override
Lifecycle getLifecycle();
- void wakeUpIfDozing(long time, View where, String why);
+ /**
+ * Wakes up the device if the device was dozing.
+ */
+ void wakeUpIfDozing(long time, View where, String why, @PowerManager.WakeReason int wakeReason);
NotificationShadeWindowView getNotificationShadeWindowView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index c1ed10c..22ebcab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -887,8 +887,6 @@
mKeyguardIndicationController.init();
mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
- mStatusBarStateController.addCallback(mStateListener,
- SysuiStatusBarStateController.RANK_STATUS_BAR);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
@@ -1507,10 +1505,11 @@
* @param why the reason for the wake up
*/
@Override
- public void wakeUpIfDozing(long time, View where, String why) {
+ public void wakeUpIfDozing(long time, View where, String why,
+ @PowerManager.WakeReason int wakeReason) {
if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
mPowerManager.wakeUp(
- time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
+ time, wakeReason, "com.android.systemui:" + why);
mWakeUpComingFromTouch = true;
mFalsingCollector.onScreenOnFromTouch();
}
@@ -1587,6 +1586,8 @@
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
+ mStatusBarStateController.addCallback(mStateListener,
+ SysuiStatusBarStateController.RANK_STATUS_BAR);
mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
mBiometricUnlockController.addBiometricModeListener(
new BiometricUnlockController.BiometricModeListener() {
@@ -3364,7 +3365,8 @@
mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
if (mBouncerShowing) {
- wakeUpIfDozing(SystemClock.uptimeMillis(), null, "BOUNCER_VISIBLE");
+ wakeUpIfDozing(SystemClock.uptimeMillis(), null, "BOUNCER_VISIBLE",
+ PowerManager.WAKE_REASON_GESTURE);
}
updateScrimController();
if (!mBouncerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index c7be219..c72eb05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -42,6 +42,8 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
import java.util.ArrayList;
import java.util.List;
@@ -56,14 +58,17 @@
private final int mIconSize;
private StatusBarWifiView mWifiView;
+ private ModernStatusBarWifiView mModernWifiView;
private boolean mDemoMode;
private int mColor;
private final MobileIconsViewModel mMobileIconsViewModel;
+ private final StatusBarLocation mLocation;
public DemoStatusIcons(
LinearLayout statusIcons,
MobileIconsViewModel mobileIconsViewModel,
+ StatusBarLocation location,
int iconSize
) {
super(statusIcons.getContext());
@@ -71,6 +76,7 @@
mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
mMobileIconsViewModel = mobileIconsViewModel;
+ mLocation = location;
if (statusIcons instanceof StatusIconContainer) {
setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -233,14 +239,14 @@
public void addDemoWifiView(WifiIconState state) {
Log.d(TAG, "addDemoWifiView: ");
- // TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}.
StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
int viewIndex = getChildCount();
// If we have mobile views, put wifi before them
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
- if (child instanceof StatusBarMobileView) {
+ if (child instanceof StatusBarMobileView
+ || child instanceof ModernStatusBarMobileView) {
viewIndex = i;
break;
}
@@ -287,7 +293,7 @@
ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
mobileContext,
"mobile",
- mMobileIconsViewModel.viewModelForSub(subId)
+ mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
// mobile always goes at the end
@@ -296,6 +302,30 @@
}
/**
+ * Add a {@link ModernStatusBarWifiView}
+ */
+ public void addModernWifiView(LocationBasedWifiViewModel viewModel) {
+ Log.d(TAG, "addModernDemoWifiView: ");
+ ModernStatusBarWifiView view = ModernStatusBarWifiView
+ .constructAndBind(mContext, "wifi", viewModel);
+
+ int viewIndex = getChildCount();
+ // If we have mobile views, put wifi before them
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof StatusBarMobileView
+ || child instanceof ModernStatusBarMobileView) {
+ viewIndex = i;
+ break;
+ }
+ }
+
+ mModernWifiView = view;
+ mModernWifiView.setStaticDrawableColor(mColor);
+ addView(view, viewIndex, createLayoutParams());
+ }
+
+ /**
* Apply an update to a mobile icon view for the given {@link MobileIconState}. For
* compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
* update it, since the context (and thus the {@link Configuration}) may have changed
@@ -317,8 +347,14 @@
public void onRemoveIcon(StatusIconDisplayable view) {
if (view.getSlot().equals("wifi")) {
- removeView(mWifiView);
- mWifiView = null;
+ if (view instanceof StatusBarWifiView) {
+ removeView(mWifiView);
+ mWifiView = null;
+ } else if (view instanceof ModernStatusBarWifiView) {
+ Log.d(TAG, "onRemoveIcon: removing modern wifi view");
+ removeView(mModernWifiView);
+ mModernWifiView = null;
+ }
} else if (view instanceof StatusBarMobileView) {
StatusBarMobileView mobileView = matchingMobileView(view);
if (mobileView != null) {
@@ -371,8 +407,14 @@
if (mWifiView != null) {
mWifiView.onDarkChanged(areas, darkIntensity, tint);
}
+ if (mModernWifiView != null) {
+ mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
+ }
for (StatusBarMobileView view : mMobileViews) {
view.onDarkChanged(areas, darkIntensity, tint);
}
+ for (ModernStatusBarMobileView view : mModernMobileViews) {
+ view.onDarkChanged(areas, darkIntensity, tint);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 2ce1163..e4227dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -30,6 +30,7 @@
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
/**
* Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -65,12 +66,14 @@
falsingManager: FalsingManager? = null,
lockIconViewController: LockIconViewController? = null,
messageDisplayer: MessageDisplayer? = null,
+ vibratorHelper: VibratorHelper? = null,
) {
binding =
bind(
this,
viewModel,
falsingManager,
+ vibratorHelper,
) {
messageDisplayer?.display(it)
}
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 df3ab49..1a14a036 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -359,6 +359,7 @@
// Whether or not these icons show up in dumpsys
protected boolean mShouldLog = false;
private StatusBarIconController mController;
+ private final StatusBarLocation mLocation;
// Enables SystemUI demo mode to take effect in this group
protected boolean mDemoable = true;
@@ -381,11 +382,12 @@
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
+ mLocation = location;
if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
- mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+ mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else {
mMobileIconsViewModel = null;
@@ -394,7 +396,7 @@
if (statusBarPipelineFlags.runNewWifiIconBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
- mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+ mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
} else {
mWifiViewModel = null;
}
@@ -495,6 +497,11 @@
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
mGroup.addView(view, index, onCreateLayoutParams());
+
+ if (mIsInDemoMode) {
+ mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+ }
+
return view;
}
@@ -569,7 +576,7 @@
.constructAndBind(
mobileContext,
slot,
- mMobileIconsViewModel.viewModelForSub(subId)
+ mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
}
@@ -686,6 +693,9 @@
mIsInDemoMode = true;
if (mDemoStatusIcons == null) {
mDemoStatusIcons = createDemoStatusIcons();
+ if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+ mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+ }
}
mDemoStatusIcons.onDemoModeStarted();
}
@@ -705,7 +715,12 @@
}
protected DemoStatusIcons createDemoStatusIcons() {
- return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize);
+ return new DemoStatusIcons(
+ (LinearLayout) mGroup,
+ mMobileIconsViewModel,
+ mLocation,
+ mIconSize
+ );
}
}
}
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 a1e0c50..da1c361 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -20,6 +20,7 @@
import android.app.KeyguardManager;
import android.content.Context;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -270,7 +271,8 @@
boolean nowExpanded) {
mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
mCentralSurfaces.wakeUpIfDozing(
- SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK");
+ SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK",
+ PowerManager.WAKE_REASON_GESTURE);
if (nowExpanded) {
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index c350c78..0d01715 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -36,7 +36,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import dagger.Binds
@@ -56,7 +56,7 @@
@Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
- @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+ @Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
@Binds
abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 6c37f94..1aa954f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -26,6 +26,8 @@
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -79,4 +81,72 @@
* [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
*/
val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
-)
+) : Diffable<MobileConnectionModel> {
+ override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
+ if (prevVal.dataConnectionState != dataConnectionState) {
+ row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+ }
+
+ if (prevVal.isEmergencyOnly != isEmergencyOnly) {
+ row.logChange(COL_EMERGENCY, isEmergencyOnly)
+ }
+
+ if (prevVal.isRoaming != isRoaming) {
+ row.logChange(COL_ROAMING, isRoaming)
+ }
+
+ if (prevVal.operatorAlphaShort != operatorAlphaShort) {
+ row.logChange(COL_OPERATOR, operatorAlphaShort)
+ }
+
+ if (prevVal.isGsm != isGsm) {
+ row.logChange(COL_IS_GSM, isGsm)
+ }
+
+ if (prevVal.cdmaLevel != cdmaLevel) {
+ row.logChange(COL_CDMA_LEVEL, cdmaLevel)
+ }
+
+ if (prevVal.primaryLevel != primaryLevel) {
+ row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
+ }
+
+ if (prevVal.dataActivityDirection != dataActivityDirection) {
+ row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+ }
+
+ if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
+ row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
+ }
+
+ if (prevVal.resolvedNetworkType != resolvedNetworkType) {
+ row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+ row.logChange(COL_EMERGENCY, isEmergencyOnly)
+ row.logChange(COL_ROAMING, isRoaming)
+ row.logChange(COL_OPERATOR, operatorAlphaShort)
+ row.logChange(COL_IS_GSM, isGsm)
+ row.logChange(COL_CDMA_LEVEL, cdmaLevel)
+ row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
+ row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+ row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
+ row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
+ }
+
+ companion object {
+ const val COL_EMERGENCY = "EmergencyOnly"
+ const val COL_ROAMING = "Roaming"
+ const val COL_OPERATOR = "OperatorName"
+ const val COL_IS_GSM = "IsGsm"
+ const val COL_CDMA_LEVEL = "CdmaLevel"
+ const val COL_PRIMARY_LEVEL = "PrimaryLevel"
+ const val COL_CONNECTION_STATE = "ConnectionState"
+ const val COL_ACTIVITY_DIRECTION = "DataActivity"
+ const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
+ const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index a8cf35a..c50d82a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -21,22 +21,48 @@
import android.telephony.TelephonyManager.EXTRA_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
/**
* Encapsulates the data needed to show a network name for a mobile network. The data is parsed from
* the intent sent by [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED].
*/
-sealed interface NetworkNameModel {
+sealed interface NetworkNameModel : Diffable<NetworkNameModel> {
val name: String
/** The default name is read from [com.android.internal.R.string.lockscreen_carrier_default] */
- data class Default(override val name: String) : NetworkNameModel
+ data class Default(override val name: String) : NetworkNameModel {
+ override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+ if (prevVal !is Default || prevVal.name != name) {
+ row.logChange(COL_NETWORK_NAME, "Default($name)")
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_NETWORK_NAME, "Default($name)")
+ }
+ }
/**
* This name has been derived from telephony intents. see
* [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]
*/
- data class Derived(override val name: String) : NetworkNameModel
+ data class Derived(override val name: String) : NetworkNameModel {
+ override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+ if (prevVal !is Derived || prevVal.name != name) {
+ row.logChange(COL_NETWORK_NAME, "Derived($name)")
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_NETWORK_NAME, "Derived($name)")
+ }
+ }
+
+ companion object {
+ const val COL_NETWORK_NAME = "networkName"
+ }
}
fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 2fd415e6..40e9ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -20,6 +20,7 @@
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.Flow
@@ -39,6 +40,13 @@
interface MobileConnectionRepository {
/** The subscriptionId that this connection represents */
val subId: Int
+
+ /**
+ * The table log buffer created for this connection. Will have the name "MobileConnectionLog
+ * [subId]"
+ */
+ val tableLogBuffer: TableLogBuffer
+
/**
* A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
* listener + model.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index d3ee85f..b252de8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -24,6 +24,8 @@
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
@@ -60,6 +62,7 @@
private val dataSource: DemoModeMobileConnectionDataSource,
@Application private val scope: CoroutineScope,
context: Context,
+ private val logFactory: TableLogBufferFactory,
) : MobileConnectionsRepository {
private var demoCommandJob: Job? = null
@@ -149,7 +152,16 @@
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
return connectionRepoCache[subId]
- ?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it }
+ ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it }
+ }
+
+ private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
+ val tableLogBuffer = logFactory.create("DemoMobileConnectionLog [$subId]", 100)
+
+ return DemoMobileConnectionRepository(
+ subId,
+ tableLogBuffer,
+ )
}
override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
@@ -260,7 +272,10 @@
}
}
-class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+class DemoMobileConnectionRepository(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepository {
override val connectionInfo = MutableStateFlow(MobileConnectionModel())
override val dataEnabled = MutableStateFlow(true)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index a1ae8ed..d4ddb85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -24,10 +24,8 @@
import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
@@ -35,8 +33,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
@@ -52,27 +48,7 @@
demoModeController: DemoModeController,
@Application scope: CoroutineScope,
) {
- private val demoCommandStream: Flow<Bundle> = conflatedCallbackFlow {
- val callback =
- object : DemoMode {
- override fun demoCommands(): List<String> = listOf(COMMAND_NETWORK)
-
- override fun dispatchDemoCommand(command: String, args: Bundle) {
- trySend(args)
- }
-
- override fun onDemoModeFinished() {
- // Handled elsewhere
- }
-
- override fun onDemoModeStarted() {
- // Handled elsewhere
- }
- }
-
- demoModeController.addCallback(callback)
- awaitClose { demoModeController.removeCallback(callback) }
- }
+ private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
// If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
// commands work and it's a little silly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 7e9a9ce..0b9e158 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -36,6 +36,9 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -46,7 +49,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -59,6 +61,7 @@
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
@@ -79,6 +82,7 @@
mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
+ mobileLogger: TableLogBuffer,
scope: CoroutineScope,
) : MobileConnectionRepository {
init {
@@ -92,10 +96,11 @@
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+ override val tableLogBuffer: TableLogBuffer = mobileLogger
+
override val connectionInfo: StateFlow<MobileConnectionModel> = run {
var state = MobileConnectionModel()
conflatedCallbackFlow {
- // TODO (b/240569788): log all of these into the connectivity logger
val callback =
object :
TelephonyCallback(),
@@ -106,6 +111,7 @@
TelephonyCallback.CarrierNetworkListener,
TelephonyCallback.DisplayInfoListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
+ logger.logOnServiceStateChanged(serviceState, subId)
state =
state.copy(
isEmergencyOnly = serviceState.isEmergencyOnly,
@@ -116,6 +122,7 @@
}
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ logger.logOnSignalStrengthsChanged(signalStrength, subId)
val cdmaLevel =
signalStrength
.getCellSignalStrengths(CellSignalStrengthCdma::class.java)
@@ -142,12 +149,14 @@
dataState: Int,
networkType: Int
) {
+ logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
state =
state.copy(dataConnectionState = dataState.toDataConnectionType())
trySend(state)
}
override fun onDataActivity(direction: Int) {
+ logger.logOnDataActivity(direction, subId)
state =
state.copy(
dataActivityDirection = direction.toMobileDataActivityModel()
@@ -156,6 +165,7 @@
}
override fun onCarrierNetworkChange(active: Boolean) {
+ logger.logOnCarrierNetworkChange(active, subId)
state = state.copy(carrierNetworkChangeActive = active)
trySend(state)
}
@@ -163,6 +173,7 @@
override fun onDisplayInfoChanged(
telephonyDisplayInfo: TelephonyDisplayInfo
) {
+ logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
val networkType =
if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
@@ -193,7 +204,11 @@
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
.onEach { telephonyCallbackEvent.tryEmit(Unit) }
- .logOutputChange(logger, "MobileSubscriptionModel")
+ .logDiffsForTable(
+ mobileLogger,
+ columnPrefix = "MobileConnection ($subId)",
+ initialValue = state,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
@@ -243,19 +258,43 @@
intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
}
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ mobileLogger,
+ columnPrefix = "",
+ initialValue = defaultNetworkName,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
- override val dataEnabled: StateFlow<Boolean> =
+ override val dataEnabled: StateFlow<Boolean> = run {
+ val initial = dataConnectionAllowed()
telephonyPollingEvent
.mapLatest { dataConnectionAllowed() }
- .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ mobileLogger,
+ columnPrefix = "",
+ columnName = "dataEnabled",
+ initialValue = initial,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+ }
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
- override val isDefaultDataSubscription: StateFlow<Boolean> =
+ override val isDefaultDataSubscription: StateFlow<Boolean> = run {
+ val initialValue = defaultDataSubId.value == subId
defaultDataSubId
.mapLatest { it == subId }
- .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ mobileLogger,
+ columnPrefix = "",
+ columnName = "isDefaultDataSub",
+ initialValue = initialValue,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue)
+ }
class Factory
@Inject
@@ -266,6 +305,7 @@
private val logger: ConnectivityPipelineLogger,
private val globalSettings: GlobalSettings,
private val mobileMappingsProxy: MobileMappingsProxy,
+ private val logFactory: TableLogBufferFactory,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) {
@@ -276,6 +316,8 @@
defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository {
+ val mobileLogger = logFactory.create(tableBufferLogName(subId), 100)
+
return MobileConnectionRepositoryImpl(
context,
subId,
@@ -289,8 +331,13 @@
mobileMappingsProxy,
bgDispatcher,
logger,
+ mobileLogger,
scope,
)
}
}
+
+ companion object {
+ fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index a9b3d18..d407abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -120,6 +121,7 @@
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
}
.mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
+ .logInputChange(logger, "onSubscriptionsChanged")
.onEach { infos -> dropUnusedReposFromCache(infos) }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
@@ -136,6 +138,8 @@
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .distinctUntilChanged()
+ .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
@@ -149,6 +153,7 @@
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
}
.distinctUntilChanged()
+ .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn(
scope,
@@ -157,13 +162,15 @@
)
private val carrierConfigChangedEvent =
- broadcastDispatcher.broadcastFlow(
- IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
- )
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED))
+ .logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
override val defaultDataSubRatConfig: StateFlow<Config> =
merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) }
+ .distinctUntilChanged()
+ .logInputChange(logger, "defaultDataSubRatConfig")
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -171,10 +178,16 @@
)
override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> =
- defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) }
+ defaultDataSubRatConfig
+ .map { mobileMappingsProxy.mapIconSets(it) }
+ .distinctUntilChanged()
+ .logInputChange(logger, "defaultMobileIconMapping")
override val defaultMobileIconGroup: Flow<MobileIconGroup> =
- defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) }
+ defaultDataSubRatConfig
+ .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .distinctUntilChanged()
+ .logInputChange(logger, "defaultMobileIconGroup")
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
if (!isValidSubId(subId)) {
@@ -191,22 +204,24 @@
* In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
* connection repositories also observe the URI for [MOBILE_DATA] + subId.
*/
- override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
+ override val globalMobileDataSettingChangedEvent: Flow<Unit> =
+ conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor(MOBILE_DATA),
+ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
}
-
- globalSettings.registerContentObserver(
- globalSettings.getUriFor(MOBILE_DATA),
- true,
- observer
- )
-
- awaitClose { context.contentResolver.unregisterContentObserver(observer) }
- }
+ .logInputChange(logger, "globalMobileDataSettingChangedEvent")
@SuppressLint("MissingPermission")
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -236,6 +251,8 @@
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
+ .distinctUntilChanged()
+ .logInputChange(logger, "defaultMobileNetworkConnectivity")
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
private fun isValidSubId(subId: Int): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 76e6a96a..e6686dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -19,6 +19,7 @@
import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -35,6 +36,9 @@
import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
+ /** The table log created for this connection */
+ val tableLogBuffer: TableLogBuffer
+
/** The current mobile data activity */
val activity: Flow<DataActivityModel>
@@ -97,6 +101,8 @@
) : MobileIconInteractor {
private val connectionInfo = connectionRepository.connectionInfo
+ override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
+
override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 62fa723..829a5ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,7 +20,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
-import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
@@ -70,6 +69,9 @@
private val mobileSubIdsState: StateFlow<List<Int>> =
mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+ /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
+ val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
+
override fun start() {
// Only notify the icon controller if we want to *render* the new icons.
// Note that this flow may still run if
@@ -81,12 +83,4 @@
}
}
}
-
- /**
- * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
- * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
- * the old view system.
- */
- fun createMobileIconsViewModel(): MobileIconsViewModel =
- iconsViewModelFactory.create(mobileSubIdsState)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 545e624..ab442b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -30,7 +30,7 @@
import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -39,7 +39,7 @@
@JvmStatic
fun bind(
view: ViewGroup,
- viewModel: MobileIconViewModel,
+ viewModel: LocationBasedMobileViewModel,
) {
val activityContainer = view.requireViewById<View>(R.id.inout_container)
val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index 0ab7bcd..e86fee2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -24,7 +24,7 @@
import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import java.util.ArrayList
class ModernStatusBarMobileView(
@@ -71,7 +71,7 @@
fun constructAndBind(
context: Context,
slot: String,
- viewModel: MobileIconViewModel,
+ viewModel: LocationBasedMobileViewModel,
): ModernStatusBarMobileView {
return (LayoutInflater.from(context)
.inflate(R.layout.status_bar_mobile_signal_group_new, null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
new file mode 100644
index 0000000..b0dc41f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
+ * allows the mobile icon to change some view parameters at different locations
+ *
+ * @param commonImpl for convenience, this class wraps a base interface that can provides all of the
+ * common implementations between locations. See [MobileIconViewModel]
+ */
+abstract class LocationBasedMobileViewModel(
+ val commonImpl: MobileIconViewModelCommon,
+ val logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon by commonImpl {
+ abstract val tint: Flow<Int>
+
+ companion object {
+ fun viewModelForLocation(
+ commonImpl: MobileIconViewModelCommon,
+ logger: ConnectivityPipelineLogger,
+ loc: StatusBarLocation,
+ ): LocationBasedMobileViewModel =
+ when (loc) {
+ StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger)
+ StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger)
+ StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger)
+ }
+ }
+}
+
+class HomeMobileIconViewModel(
+ commonImpl: MobileIconViewModelCommon,
+ logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+ override val tint: Flow<Int> =
+ flowOf(Color.CYAN)
+ .distinctUntilChanged()
+ .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})")
+}
+
+class QsMobileIconViewModel(
+ commonImpl: MobileIconViewModelCommon,
+ logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+ override val tint: Flow<Int> =
+ flowOf(Color.GREEN)
+ .distinctUntilChanged()
+ .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})")
+}
+
+class KeyguardMobileIconViewModel(
+ commonImpl: MobileIconViewModelCommon,
+ logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+ override val tint: Flow<Int> =
+ flowOf(Color.MAGENTA)
+ .distinctUntilChanged()
+ .logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 961283f..2d6ac4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -16,23 +16,40 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
-import android.graphics.Color
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Common interface for all of the location-based mobile icon view models. */
+interface MobileIconViewModelCommon {
+ val subscriptionId: Int
+ /** An int consumable by [SignalDrawable] for display */
+ val iconId: Flow<Int>
+ val roaming: Flow<Boolean>
+ /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
+ val networkTypeIcon: Flow<Icon?>
+ val activityInVisible: Flow<Boolean>
+ val activityOutVisible: Flow<Boolean>
+ val activityContainerVisible: Flow<Boolean>
+}
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -40,25 +57,29 @@
* subscription's information.
*
* There will be exactly one [MobileIconViewModel] per filtered subscription offered from
- * [MobileIconsInteractor.filteredSubscriptions]
+ * [MobileIconsInteractor.filteredSubscriptions].
*
- * TODO: figure out where carrier merged and VCN models go (probably here?)
+ * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
+ * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
+ * model gets the exact same information, as well as allows us to log that unified state only once
+ * per icon.
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel
constructor(
- val subscriptionId: Int,
+ override val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger,
constants: ConnectivityConstants,
-) {
+ scope: CoroutineScope,
+) : MobileIconViewModelCommon {
/** Whether or not to show the error state of [SignalDrawable] */
private val showExclamationMark: Flow<Boolean> =
iconInteractor.isDefaultDataEnabled.mapLatest { !it }
- /** An int consumable by [SignalDrawable] for display */
- val iconId: Flow<Int> =
+ override val iconId: Flow<Int> = run {
+ val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
level,
numberOfLevels,
@@ -66,32 +87,56 @@
SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
}
.distinctUntilChanged()
- .logOutputChange(logger, "iconId($subscriptionId)")
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "iconId",
+ initialValue = initial,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+ }
- /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- val networkTypeIcon: Flow<Icon?> =
+ override val networkTypeIcon: Flow<Icon?> =
combine(
- iconInteractor.networkTypeIconGroup,
- iconInteractor.isDataConnected,
- iconInteractor.isDataEnabled,
- iconInteractor.isDefaultConnectionFailed,
- iconInteractor.alwaysShowDataRatIcon,
- ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
- val desc =
- if (networkTypeIconGroup.dataContentDescription != 0)
- ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
- else null
- val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
- return@combine when {
- alwaysShow -> icon
- !dataConnected -> null
- !dataEnabled -> null
- failedConnection -> null
- else -> icon
+ iconInteractor.networkTypeIconGroup,
+ iconInteractor.isDataConnected,
+ iconInteractor.isDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ iconInteractor.alwaysShowDataRatIcon,
+ ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
+ val desc =
+ if (networkTypeIconGroup.dataContentDescription != 0)
+ ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+ else null
+ val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
+ return@combine when {
+ alwaysShow -> icon
+ !dataConnected -> null
+ !dataEnabled -> null
+ failedConnection -> null
+ else -> icon
+ }
}
- }
+ .distinctUntilChanged()
+ .onEach {
+ // This is done as an onEach side effect since Icon is not Diffable (yet)
+ iconInteractor.tableLogBuffer.logChange(
+ prefix = "",
+ columnName = "networkTypeIcon",
+ value = it.toString(),
+ )
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
- val roaming: Flow<Boolean> = iconInteractor.isRoaming
+ override val roaming: StateFlow<Boolean> =
+ iconInteractor.isRoaming
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "roaming",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
private val activity: Flow<DataActivityModel?> =
if (!constants.shouldShowActivityConfig) {
@@ -100,10 +145,39 @@
iconInteractor.activity
}
- val activityInVisible: Flow<Boolean> = activity.map { it?.hasActivityIn ?: false }
- val activityOutVisible: Flow<Boolean> = activity.map { it?.hasActivityOut ?: false }
- val activityContainerVisible: Flow<Boolean> =
- activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+ override val activityInVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityIn ?: false }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "activityInVisible",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
- val tint: Flow<Int> = flowOf(Color.CYAN)
+ override val activityOutVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityOut ?: false }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "activityOutVisible",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val activityContainerVisible: Flow<Boolean> =
+ activity
+ .map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "activityContainerVisible",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 0b41d31..b9318b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-@file:OptIn(InternalCoroutinesApi::class)
-
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
-import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
/**
* View model for describing the system's current mobile cellular connections. The result is a list
@@ -38,15 +40,33 @@
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
+ @Application private val scope: CoroutineScope,
) {
- /** TODO: do we need to cache these? */
- fun viewModelForSub(subId: Int): MobileIconViewModel =
- MobileIconViewModel(
- subId,
- interactor.createMobileConnectionInteractorForSubId(subId),
- logger,
- constants,
- )
+ @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
+
+ init {
+ scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
+ }
+
+ fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
+ val common =
+ mobileIconSubIdCache[subId]
+ ?: MobileIconViewModel(
+ subId,
+ interactor.createMobileConnectionInteractorForSubId(subId),
+ logger,
+ constants,
+ scope,
+ )
+ .also { mobileIconSubIdCache[subId] = it }
+
+ return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location)
+ }
+
+ private fun removeInvalidModelsFromCache(subIds: List<Int>) {
+ val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
+ subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
+ }
class Factory
@Inject
@@ -54,6 +74,7 @@
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
+ @Application private val scope: CoroutineScope,
) {
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
@@ -61,6 +82,7 @@
interactor,
logger,
constants,
+ scope,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index d3cf32f..d3ff357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -18,8 +18,11 @@
import android.net.Network
import android.net.NetworkCapabilities
-import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyDisplayInfo
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
@@ -28,7 +31,9 @@
import kotlinx.coroutines.flow.onEach
@SysUISingleton
-class ConnectivityPipelineLogger @Inject constructor(
+class ConnectivityPipelineLogger
+@Inject
+constructor(
@StatusBarConnectivityLog private val buffer: LogBuffer,
) {
/**
@@ -37,34 +42,23 @@
* Use this method for inputs that don't have any extra information besides their callback name.
*/
fun logInputChange(callbackName: String) {
+ buffer.log(SB_LOGGING_TAG, LogLevel.INFO, { str1 = callbackName }, { "Input: $str1" })
+ }
+
+ /** Logs a change in one of the **raw inputs** to the connectivity pipeline. */
+ fun logInputChange(callbackName: String, changeInfo: String?) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
- { str1 = callbackName },
- { "Input: $str1" }
+ {
+ str1 = callbackName
+ str2 = changeInfo
+ },
+ { "Input: $str1: $str2" }
)
}
- /**
- * Logs a change in one of the **raw inputs** to the connectivity pipeline.
- */
- fun logInputChange(callbackName: String, changeInfo: String?) {
- buffer.log(
- SB_LOGGING_TAG,
- LogLevel.INFO,
- {
- str1 = callbackName
- str2 = changeInfo
- },
- {
- "Input: $str1: $str2"
- }
- )
- }
-
- /**
- * Logs a **data transformation** that we performed within the connectivity pipeline.
- */
+ /** Logs a **data transformation** that we performed within the connectivity pipeline. */
fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) {
if (oldValue == newValue) {
buffer.log(
@@ -74,9 +68,7 @@
str1 = transformationName
str2 = oldValue.toString()
},
- {
- "Transform: $str1: $str2 (transformation didn't change it)"
- }
+ { "Transform: $str1: $str2 (transformation didn't change it)" }
)
} else {
buffer.log(
@@ -87,27 +79,21 @@
str2 = oldValue.toString()
str3 = newValue.toString()
},
- {
- "Transform: $str1: $str2 -> $str3"
- }
+ { "Transform: $str1: $str2 -> $str3" }
)
}
}
- /**
- * Logs a change in one of the **outputs** to the connectivity pipeline.
- */
+ /** Logs a change in one of the **outputs** to the connectivity pipeline. */
fun logOutputChange(outputParamName: String, changeInfo: String) {
buffer.log(
- SB_LOGGING_TAG,
- LogLevel.INFO,
- {
- str1 = outputParamName
- str2 = changeInfo
- },
- {
- "Output: $str1: $str2"
- }
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ str1 = outputParamName
+ str2 = changeInfo
+ },
+ { "Output: $str1: $str2" }
)
}
@@ -119,9 +105,7 @@
int1 = network.getNetId()
str1 = networkCapabilities.toString()
},
- {
- "onCapabilitiesChanged: net=$int1 capabilities=$str1"
- }
+ { "onCapabilitiesChanged: net=$int1 capabilities=$str1" }
)
}
@@ -129,21 +113,93 @@
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
+ { int1 = network.getNetId() },
+ { "onLost: net=$int1" }
+ )
+ }
+
+ fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
{
- int1 = network.getNetId()
+ int1 = subId
+ bool1 = serviceState.isEmergencyOnly
+ bool2 = serviceState.roaming
+ str1 = serviceState.operatorAlphaShort
},
{
- "onLost: net=$int1"
+ "onServiceStateChanged: subId=$int1 emergencyOnly=$bool1 roaming=$bool2" +
+ " operator=$str1"
}
)
}
+ fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = subId
+ str1 = signalStrength.toString()
+ },
+ { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" }
+ )
+ }
+
+ fun logOnDataConnectionStateChanged(dataState: Int, networkType: Int, subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = subId
+ int2 = dataState
+ str1 = networkType.toString()
+ },
+ { "onDataConnectionStateChanged: subId=$int1 dataState=$int2 networkType=$str1" },
+ )
+ }
+
+ fun logOnDataActivity(direction: Int, subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = subId
+ int2 = direction
+ },
+ { "onDataActivity: subId=$int1 direction=$int2" },
+ )
+ }
+
+ fun logOnCarrierNetworkChange(active: Boolean, subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = subId
+ bool1 = active
+ },
+ { "onCarrierNetworkChange: subId=$int1 active=$bool1" },
+ )
+ }
+
+ fun logOnDisplayInfoChanged(displayInfo: TelephonyDisplayInfo, subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = subId
+ str1 = displayInfo.toString()
+ },
+ { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1" },
+ )
+ }
+
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"
- /**
- * Log a change in one of the **inputs** to the connectivity pipeline.
- */
+ /** Log a change in one of the **inputs** to the connectivity pipeline. */
fun Flow<Unit>.logInputChange(
logger: ConnectivityPipelineLogger,
inputParamName: String,
@@ -155,26 +211,26 @@
* Log a change in one of the **inputs** to the connectivity pipeline.
*
* @param prettyPrint an optional function to transform the value into a readable string.
- * [toString] is used if no custom function is provided.
+ * [toString] is used if no custom function is provided.
*/
fun <T> Flow<T>.logInputChange(
logger: ConnectivityPipelineLogger,
inputParamName: String,
prettyPrint: (T) -> String = { it.toString() }
): Flow<T> {
- return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
+ return this.onEach { logger.logInputChange(inputParamName, prettyPrint(it)) }
}
/**
* Log a change in one of the **outputs** to the connectivity pipeline.
*
* @param prettyPrint an optional function to transform the value into a readable string.
- * [toString] is used if no custom function is provided.
+ * [toString] is used if no custom function is provided.
*/
fun <T> Flow<T>.logOutputChange(
- logger: ConnectivityPipelineLogger,
- outputParamName: String,
- prettyPrint: (T) -> String = { it.toString() }
+ logger: ConnectivityPipelineLogger,
+ outputParamName: String,
+ prettyPrint: (T) -> String = { it.toString() }
): Flow<T> {
return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
new file mode 100644
index 0000000..73bcdfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides the [WifiRepository] interface either through the [DemoWifiRepository] implementation,
+ * or the [WifiRepositoryImpl]'s prod implementation, based on the current demo mode value. In this
+ * way, downstream clients can all consist of real implementations and not care about which
+ * repository is responsible for the data. Graphically:
+ *
+ * ```
+ * RealRepository
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
+ * DemoRepository
+ * ```
+ *
+ * When demo mode turns on, every flow will [flatMapLatest] to the current provider's version of
+ * that flow.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class WifiRepositorySwitcher
+@Inject
+constructor(
+ private val realImpl: WifiRepositoryImpl,
+ private val demoImpl: DemoWifiRepository,
+ private val demoModeController: DemoModeController,
+ @Application scope: CoroutineScope,
+) : WifiRepository {
+ private val isDemoMode =
+ conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+ // Don't care
+ }
+
+ override fun onDemoModeStarted() {
+ demoImpl.startProcessingCommands()
+ trySend(true)
+ }
+
+ override fun onDemoModeFinished() {
+ demoImpl.stopProcessingCommands()
+ trySend(false)
+ }
+ }
+
+ demoModeController.addCallback(callback)
+ awaitClose { demoModeController.removeCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+ @VisibleForTesting
+ val activeRepo =
+ isDemoMode
+ .mapLatest { isDemoMode ->
+ if (isDemoMode) {
+ demoImpl
+ } else {
+ realImpl
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+
+ override val isWifiEnabled: StateFlow<Boolean> =
+ activeRepo
+ .flatMapLatest { it.isWifiEnabled }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiEnabled.value)
+
+ override val isWifiDefault: StateFlow<Boolean> =
+ activeRepo
+ .flatMapLatest { it.isWifiDefault }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiDefault.value)
+
+ override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ activeRepo
+ .flatMapLatest { it.wifiNetwork }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
+
+ override val wifiActivity: StateFlow<DataActivityModel> =
+ activeRepo
+ .flatMapLatest { it.wifiActivity }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
new file mode 100644
index 0000000..c588945
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo
+
+import android.net.wifi.WifiManager
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** Data source to map between demo mode commands and inputs into [DemoWifiRepository]'s flows */
+@SysUISingleton
+class DemoModeWifiDataSource
+@Inject
+constructor(
+ demoModeController: DemoModeController,
+ @Application scope: CoroutineScope,
+) {
+ private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
+ private val _wifiCommands = demoCommandStream.map { args -> args.toWifiEvent() }
+ val wifiEvents = _wifiCommands.shareIn(scope, SharingStarted.WhileSubscribed())
+
+ private fun Bundle.toWifiEvent(): FakeWifiEventModel? {
+ val wifi = getString("wifi") ?: return null
+ return if (wifi == "show") {
+ activeWifiEvent()
+ } else {
+ FakeWifiEventModel.WifiDisabled
+ }
+ }
+
+ private fun Bundle.activeWifiEvent(): FakeWifiEventModel.Wifi {
+ val level = getString("level")?.toInt()
+ val activity = getString("activity")?.toActivity()
+ val ssid = getString("ssid")
+ val validated = getString("fully").toBoolean()
+
+ return FakeWifiEventModel.Wifi(
+ level = level,
+ activity = activity,
+ ssid = ssid,
+ validated = validated,
+ )
+ }
+
+ private fun String.toActivity(): Int =
+ when (this) {
+ "inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
+ "in" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN
+ "out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT
+ else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
new file mode 100644
index 0000000..7890074
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/** Demo-able wifi repository to support SystemUI demo mode commands. */
+class DemoWifiRepository
+@Inject
+constructor(
+ private val dataSource: DemoModeWifiDataSource,
+ @Application private val scope: CoroutineScope,
+) : WifiRepository {
+ private var demoCommandJob: Job? = null
+
+ private val _isWifiEnabled = MutableStateFlow(false)
+ override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+
+ private val _isWifiDefault = MutableStateFlow(false)
+ override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
+
+ private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
+ override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
+
+ private val _wifiActivity =
+ MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
+
+ fun startProcessingCommands() {
+ demoCommandJob =
+ scope.launch {
+ dataSource.wifiEvents.filterNotNull().collect { event -> processEvent(event) }
+ }
+ }
+
+ fun stopProcessingCommands() {
+ demoCommandJob?.cancel()
+ }
+
+ private fun processEvent(event: FakeWifiEventModel) =
+ when (event) {
+ is FakeWifiEventModel.Wifi -> processEnabledWifiState(event)
+ is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState()
+ }
+
+ private fun processDisabledWifiState() {
+ _isWifiEnabled.value = false
+ _isWifiDefault.value = false
+ _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ _wifiNetwork.value = WifiNetworkModel.Inactive
+ }
+
+ private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) {
+ _isWifiEnabled.value = true
+ _isWifiDefault.value = true
+ _wifiActivity.value =
+ event.activity?.toWifiDataActivityModel()
+ ?: DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ _wifiNetwork.value = event.toWifiNetworkModel()
+ }
+
+ private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel =
+ WifiNetworkModel.Active(
+ networkId = DEMO_NET_ID,
+ isValidated = validated ?: true,
+ level = level,
+ ssid = ssid,
+
+ // These fields below aren't supported in demo mode, since they aren't needed to satisfy
+ // the interface.
+ isPasspointAccessPoint = false,
+ isOnlineSignUpForPasspointAccessPoint = false,
+ passpointProviderFriendlyName = null,
+ )
+
+ companion object {
+ private const val DEMO_NET_ID = 1234
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
new file mode 100644
index 0000000..2353fb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model
+
+/**
+ * Model for demo wifi commands, ported from [NetworkControllerImpl]
+ *
+ * Nullable fields represent optional command line arguments
+ */
+sealed interface FakeWifiEventModel {
+ data class Wifi(
+ val level: Int?,
+ val activity: Int?,
+ val ssid: String?,
+ val validated: Boolean?,
+ ) : FakeWifiEventModel
+
+ object WifiDisabled : FakeWifiEventModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index 3c0eb91..4f7fe28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -38,16 +38,12 @@
dumpManager.registerDumpable("${SB_LOGGING_TAG}WifiConstants", this)
}
- /** True if we should show the activityIn/activityOut icons and false otherwise. */
- val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
-
/** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
val alwaysShowIconIfEnabled =
context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
- println("shouldShowActivityConfig=$shouldShowActivityConfig")
println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 07a7595..ab464cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -148,7 +148,7 @@
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<DataActivityModel?> =
- if (!wifiConstants.shouldShowActivityConfig) {
+ if (!connectivityConstants.shouldShowActivityConfig) {
flowOf(null)
} else {
combine(interactor.activity, interactor.ssid) { activity, ssid ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index d8a8c5d..c9ed0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -47,6 +47,7 @@
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
@@ -57,7 +58,6 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
-import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -133,6 +133,7 @@
private RevealParams mRevealParams;
private Rect mContentBackgroundBounds;
private boolean mIsFocusAnimationFlagActive;
+ private boolean mIsAnimatingAppearance = false;
// TODO(b/193539698): move these to a Controller
private RemoteInputController mController;
@@ -142,10 +143,6 @@
private boolean mSending;
private NotificationViewWrapper mWrapper;
- private Integer mDefocusTargetHeight = null;
- private boolean mIsAnimatingAppearance = false;
-
-
// TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
// that need the controller shouldn't have access to the view
private RemoteInputViewController mViewController;
@@ -423,18 +420,6 @@
return mIsAnimatingAppearance;
}
- /**
- * View will ensure to use at most the provided defocusTargetHeight, when defocusing animated.
- * This is to ensure that the parent can resize itself to the targetHeight while the defocus
- * animation of the RemoteInputView is running.
- *
- * @param defocusTargetHeight The target height the parent will resize itself to. If null, the
- * RemoteInputView will not resize itself.
- */
- public void setDefocusTargetHeight(Integer defocusTargetHeight) {
- mDefocusTargetHeight = defocusTargetHeight;
- }
-
@VisibleForTesting
void onDefocus(boolean animate, boolean logClose) {
mController.removeRemoteInput(mEntry, mToken);
@@ -443,35 +428,28 @@
// During removal, we get reattached and lose focus. Not hiding in that
// case to prevent flicker.
if (!mRemoved) {
- if (animate && mIsFocusAnimationFlagActive) {
- Animator animator = getDefocusAnimator();
+ ViewGroup parent = (ViewGroup) getParent();
+ if (animate && parent != null && mIsFocusAnimationFlagActive) {
- // When defocusing, the notification needs to shrink. Therefore, we need to free
- // up the space that is needed for the RemoteInputView. This is done by setting
- // a negative top margin of the height difference of the RemoteInputView and its
- // sibling (the actions_container_layout containing the Reply button)
- if (mDefocusTargetHeight != null && mDefocusTargetHeight < getHeight()
- && mDefocusTargetHeight >= 0
- && getLayoutParams() instanceof FrameLayout.LayoutParams) {
- int heightToShrink = getHeight() - mDefocusTargetHeight;
- FrameLayout.LayoutParams layoutParams =
- (FrameLayout.LayoutParams) getLayoutParams();
- layoutParams.topMargin = -heightToShrink;
- setLayoutParams(layoutParams);
- ((ViewGroup) getParent().getParent()).setClipChildren(false);
- }
+ ViewGroup grandParent = (ViewGroup) parent.getParent();
+ ViewGroupOverlay overlay = parent.getOverlay();
+
+ // After adding this RemoteInputView to the overlay of the parent (and thus removing
+ // it from the parent itself), the parent will shrink in height. This causes the
+ // overlay to be moved. To correct the position of the overlay we need to offset it.
+ int overlayOffsetY = getMaxSiblingHeight() - getHeight();
+ overlay.add(this);
+ if (grandParent != null) grandParent.setClipChildren(false);
+
+ Animator animator = getDefocusAnimator(overlayOffsetY);
+ View self = this;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- //reset top margin after the animation
- if (getLayoutParams() instanceof FrameLayout.LayoutParams) {
- FrameLayout.LayoutParams layoutParams =
- (FrameLayout.LayoutParams) getLayoutParams();
- layoutParams.topMargin = 0;
- setLayoutParams(layoutParams);
- ((ViewGroup) getParent().getParent()).setClipChildren(true);
- }
+ overlay.remove(self);
+ parent.addView(self);
+ if (grandParent != null) grandParent.setClipChildren(true);
setVisibility(GONE);
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false);
@@ -609,7 +587,7 @@
}
/**
- * Sets whether the feature flag for the updated inline reply animation is active or not.
+ * Sets whether the feature flag for the revised inline reply animation is active or not.
* @param active
*/
public void setIsFocusAnimationFlagActive(boolean active) {
@@ -846,6 +824,23 @@
}
}
+ /**
+ * @return max sibling height (0 in case of no siblings)
+ */
+ public int getMaxSiblingHeight() {
+ ViewGroup parentView = (ViewGroup) getParent();
+ int maxHeight = 0;
+ if (parentView == null) return 0;
+ for (int i = 0; i < parentView.getChildCount(); i++) {
+ View siblingView = parentView.getChildAt(i);
+ if (siblingView != this) maxHeight = Math.max(maxHeight, siblingView.getHeight());
+ }
+ return maxHeight;
+ }
+
+ /**
+ * Creates an animator for the focus animation.
+ */
private Animator getFocusAnimator(View crossFadeView) {
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
@@ -854,7 +849,7 @@
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
scaleAnimator.addUpdateListener(valueAnimator -> {
- setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), 0);
});
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
@@ -875,21 +870,26 @@
return animatorSet;
}
- private Animator getDefocusAnimator() {
+ /**
+ * Creates an animator for the defocus animation.
+ *
+ * @param offsetY The RemoteInputView will be offset by offsetY during the animation
+ */
+ private Animator getDefocusAnimator(int offsetY) {
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
scaleAnimator.addUpdateListener(valueAnimator -> {
- setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), offsetY);
});
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
scaleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
- setFocusAnimationScaleY(1f);
+ setFocusAnimationScaleY(1f /* scaleY */, 0 /* verticalOffset */);
}
});
@@ -901,15 +901,21 @@
/**
* Sets affected view properties for a vertical scale animation
*
- * @param scaleY desired vertical view scale
+ * @param scaleY desired vertical view scale
+ * @param verticalOffset vertical offset to apply to the RemoteInputView during the animation
*/
- private void setFocusAnimationScaleY(float scaleY) {
+ private void setFocusAnimationScaleY(float scaleY, int verticalOffset) {
int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
- mContentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
+ Rect contentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
mContentView.getHeight() - verticalBoundOffset);
- mContentBackground.setBounds(mContentBackgroundBounds);
+ mContentBackground.setBounds(contentBackgroundBounds);
mContentView.setBackground(mContentBackground);
- setTranslationY(verticalBoundOffset);
+ if (scaleY == 1f) {
+ mContentBackgroundBounds = null;
+ } else {
+ mContentBackgroundBounds = contentBackgroundBounds;
+ }
+ setTranslationY(verticalBoundOffset + verticalOffset);
}
/** Handler for button click on send action in IME. */
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 1f44434..2464886 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -33,6 +33,7 @@
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeAvailabilityTracker;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.util.settings.GlobalSettings;
public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
@@ -54,13 +55,15 @@
private SwitchPreference mOnSwitch;
private DemoModeController mDemoModeController;
+ private GlobalSettings mGlobalSettings;
private Tracker mDemoModeTracker;
// We are the only ones who ever call this constructor, so don't worry about the warning
@SuppressLint("ValidFragment")
- public DemoModeFragment(DemoModeController demoModeController) {
+ public DemoModeFragment(DemoModeController demoModeController, GlobalSettings globalSettings) {
super();
mDemoModeController = demoModeController;
+ mGlobalSettings = globalSettings;
}
@@ -80,7 +83,7 @@
screen.addPreference(mOnSwitch);
setPreferenceScreen(screen);
- mDemoModeTracker = new Tracker(context);
+ mDemoModeTracker = new Tracker(context, mGlobalSettings);
mDemoModeTracker.startTracking();
updateDemoModeEnabled();
updateDemoModeOn();
@@ -202,8 +205,8 @@
}
private class Tracker extends DemoModeAvailabilityTracker {
- Tracker(Context context) {
- super(context);
+ Tracker(Context context, GlobalSettings globalSettings) {
+ super(context, globalSettings);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 3231aec..32ecb67 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -33,6 +33,7 @@
import com.android.systemui.R;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Inject;
@@ -44,12 +45,18 @@
private final DemoModeController mDemoModeController;
private final TunerService mTunerService;
+ private final GlobalSettings mGlobalSettings;
@Inject
- TunerActivity(DemoModeController demoModeController, TunerService tunerService) {
+ TunerActivity(
+ DemoModeController demoModeController,
+ TunerService tunerService,
+ GlobalSettings globalSettings
+ ) {
super();
mDemoModeController = demoModeController;
mTunerService = tunerService;
+ mGlobalSettings = globalSettings;
}
protected void onCreate(Bundle savedInstanceState) {
@@ -69,7 +76,7 @@
boolean showDemoMode = action != null && action.equals(
"com.android.settings.action.DEMO_MODE");
final PreferenceFragment fragment = showDemoMode
- ? new DemoModeFragment(mDemoModeController)
+ ? new DemoModeFragment(mDemoModeController, mGlobalSettings)
: new TunerFragment(mTunerService);
getFragmentManager().beginTransaction().replace(R.id.content_frame,
fragment, TAG_TUNER).commit();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 6c1f008..bb03f28 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -22,9 +22,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -33,7 +37,6 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
/**
@@ -50,7 +53,9 @@
@Mock private lateinit var parent: ViewGroup
- private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+ private lateinit var underTest: KeyguardUnfoldTransition
private lateinit var progressListener: TransitionProgressListener
private var xTranslationMax = 0f
@@ -61,10 +66,10 @@
xTranslationMax =
context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
- keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider)
+ underTest = KeyguardUnfoldTransition(context, statusBarStateController, progressProvider)
- keyguardUnfoldTransition.setup(parent)
- keyguardUnfoldTransition.statusViewCentered = false
+ underTest.setup(parent)
+ underTest.statusViewCentered = false
verify(progressProvider).addCallback(capture(progressListenerCaptor))
progressListener = progressListenerCaptor.value
@@ -72,10 +77,11 @@
@Test
fun onTransition_centeredViewDoesNotMove() {
- keyguardUnfoldTransition.statusViewCentered = true
+ whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+ underTest.statusViewCentered = true
val view = View(context)
- `when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+ whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
progressListener.onTransitionStarted()
assertThat(view.translationX).isZero()
@@ -89,4 +95,44 @@
progressListener.onTransitionFinished()
assertThat(view.translationX).isZero()
}
+
+ @Test
+ fun whenInShadeState_viewDoesNotMove() {
+ whenever(statusBarStateController.getState()).thenReturn(SHADE)
+
+ val view = View(context)
+ whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+
+ progressListener.onTransitionStarted()
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionProgress(0f)
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionProgress(0.5f)
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionFinished()
+ assertThat(view.translationX).isZero()
+ }
+
+ @Test
+ fun whenInKeyguardState_viewDoesMove() {
+ whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+
+ val view = View(context)
+ whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+
+ progressListener.onTransitionStarted()
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionProgress(0f)
+ assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+ progressListener.onTransitionProgress(0.5f)
+ assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+ progressListener.onTransitionFinished()
+ assertThat(view.translationX).isZero()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 898f370..b4696e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,8 +35,6 @@
import android.view.WindowManager
import android.widget.ScrollView
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
-import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
@@ -81,6 +79,8 @@
@Mock
lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock
+ lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector
+ @Mock
lateinit var windowToken: IBinder
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
@@ -170,26 +170,6 @@
}
@Test
- fun testDismissesOnFocusLoss() {
- val container = initializeFingerprintContainer()
- waitForIdleSync()
-
- val requestID = authContainer?.requestId ?: 0L
-
- verify(callback).onDialogAnimatedIn(requestID)
-
- container.onWindowFocusChanged(false)
- waitForIdleSync()
-
- verify(callback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq<ByteArray?>(null), /* credentialAttestation */
- eq(requestID)
- )
- assertThat(container.parent).isNull()
- }
-
- @Test
fun testFocusLossAfterRotating() {
val container = initializeFingerprintContainer()
waitForIdleSync()
@@ -209,35 +189,6 @@
}
@Test
- fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
- val container = initializeFingerprintContainer(
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
- )
- waitForIdleSync()
-
- val requestID = authContainer?.requestId ?: 0L
-
- // Simulate keyboard was shown on the credential view
- val windowInsetsController = container.windowInsetsController
- spyOn(windowInsetsController)
- spyOn(container.rootWindowInsets)
- doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime())
-
- container.onWindowFocusChanged(false)
- waitForIdleSync()
-
- // Expect hiding IME request will be invoked when dismissing the view
- verify(windowInsetsController)?.hide(WindowInsets.Type.ime())
-
- verify(callback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq<ByteArray?>(null), /* credentialAttestation */
- eq(requestID)
- )
- assertThat(container.parent).isNull()
- }
-
- @Test
fun testActionAuthenticated_sendsDismissedAuthenticated() {
val container = initializeFingerprintContainer()
container.mBiometricCallback.onAction(
@@ -519,6 +470,7 @@
fingerprintProps,
faceProps,
wakefulnessLifecycle,
+ panelInteractionDetector,
userManager,
lockPatternUtils,
interactionJankMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 67b293f44..5afe49e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -152,6 +152,8 @@
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
+ private AuthDialogPanelInteractionDetector mPanelInteractionDetector;
+ @Mock
private UserManager mUserManager;
@Mock
private LockPatternUtils mLockPatternUtils;
@@ -953,9 +955,10 @@
super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
mFingerprintManager, mFaceManager, () -> mUdfpsController,
() -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
- mUserManager, mLockPatternUtils, mUdfpsLogger, mLogContextInteractor,
- () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel,
- mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper);
+ mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
+ mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
+ () -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
+ mBackgroundExecutor, mVibratorHelper);
}
@Override
@@ -963,7 +966,9 @@
boolean requireConfirmation, int userId, int[] sensorIds,
String opPackageName, boolean skipIntro, long operationId, long requestId,
@BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
- WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ AuthDialogPanelInteractionDetector panelInteractionDetector,
+ UserManager userManager,
LockPatternUtils lockPatternUtils) {
mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index e7e6918..bdd496e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -18,6 +18,8 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
+import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -32,6 +34,7 @@
import android.content.ClipboardManager;
import android.os.PersistableBundle;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -66,6 +69,8 @@
@Mock
private ClipboardOverlayController mOverlayController;
@Mock
+ private ClipboardToast mClipboardToast;
+ @Mock
private UiEventLogger mUiEventLogger;
@Mock
private FeatureFlags mFeatureFlags;
@@ -84,6 +89,8 @@
@Spy
private Provider<ClipboardOverlayController> mOverlayControllerProvider;
+ private ClipboardListener mClipboardListener;
+
@Before
public void setup() {
@@ -93,7 +100,8 @@
when(mClipboardOverlayControllerLegacyFactory.create(any()))
.thenReturn(mOverlayControllerLegacy);
when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
-
+ Settings.Secure.putInt(
+ mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1);
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
new ClipData.Item("Test Item"));
@@ -101,16 +109,17 @@
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
mDeviceConfigProxy = new DeviceConfigProxyFake();
+
+ mClipboardListener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardToast, mClipboardManager, mUiEventLogger, mFeatureFlags);
}
@Test
public void test_disabled() {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"false", false);
- ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
- mClipboardManager, mUiEventLogger, mFeatureFlags);
- listener.start();
+ mClipboardListener.start();
verifyZeroInteractions(mClipboardManager);
verifyZeroInteractions(mUiEventLogger);
}
@@ -119,10 +128,7 @@
public void test_enabled() {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
- ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
- mClipboardManager, mUiEventLogger, mFeatureFlags);
- listener.start();
+ mClipboardListener.start();
verify(mClipboardManager).addPrimaryClipChangedListener(any());
verifyZeroInteractions(mUiEventLogger);
}
@@ -133,11 +139,8 @@
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
- ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
- mClipboardManager, mUiEventLogger, mFeatureFlags);
- listener.start();
- listener.onPrimaryClipChanged();
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
verify(mClipboardOverlayControllerLegacyFactory).create(any());
@@ -152,14 +155,14 @@
// Should clear the overlay controller
mRunnableCaptor.getValue().run();
- listener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
// Not calling the runnable here, just change the clip again and verify that the overlay is
// NOT recreated.
- listener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
verifyZeroInteractions(mOverlayControllerProvider);
@@ -171,11 +174,8 @@
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
- ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
- mClipboardManager, mUiEventLogger, mFeatureFlags);
- listener.start();
- listener.onPrimaryClipChanged();
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
verify(mOverlayControllerProvider).get();
@@ -190,14 +190,14 @@
// Should clear the overlay controller
mRunnableCaptor.getValue().run();
- listener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
verify(mOverlayControllerProvider, times(2)).get();
// Not calling the runnable here, just change the clip again and verify that the overlay is
// NOT recreated.
- listener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
verify(mOverlayControllerProvider, times(2)).get();
verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
@@ -233,13 +233,10 @@
public void test_logging_enterAndReenter() {
when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
- ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
- mClipboardManager, mUiEventLogger, mFeatureFlags);
- listener.start();
+ mClipboardListener.start();
- listener.onPrimaryClipChanged();
- listener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
@@ -251,17 +248,29 @@
public void test_logging_enterAndReenter_new() {
when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
- ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
- mClipboardManager, mUiEventLogger, mFeatureFlags);
- listener.start();
+ mClipboardListener.start();
- listener.onPrimaryClipChanged();
- listener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
+ mClipboardListener.onPrimaryClipChanged();
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
}
+
+ @Test
+ public void test_userSetupIncomplete_showsToast() {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
+ verify(mClipboardToast, times(1)).showCopiedToast();
+ verifyZeroInteractions(mOverlayControllerProvider);
+ verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
new file mode 100644
index 0000000..87c66b5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.demomode
+
+import android.content.Intent
+import android.os.Bundle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode.ACTION_DEMO
+import com.android.systemui.demomode.DemoMode.COMMAND_STATUS
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class DemoModeControllerTest : SysuiTestCase() {
+ private lateinit var underTest: DemoModeController
+
+ @Mock private lateinit var dumpManager: DumpManager
+
+ private val globalSettings = FakeSettings()
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+
+ MockitoAnnotations.initMocks(this)
+
+ globalSettings.putInt(DemoModeController.DEMO_MODE_ALLOWED, 1)
+ globalSettings.putInt(DemoModeController.DEMO_MODE_ON, 1)
+
+ underTest =
+ DemoModeController(
+ context = context,
+ dumpManager = dumpManager,
+ globalSettings = globalSettings,
+ broadcastDispatcher = fakeBroadcastDispatcher
+ )
+
+ underTest.initialize()
+ }
+
+ @Test
+ fun `demo command flow - returns args`() =
+ testScope.runTest {
+ var latest: Bundle? = null
+ val flow = underTest.demoFlowForCommand(TEST_COMMAND)
+ val job = launch { flow.collect { latest = it } }
+
+ sendDemoCommand(args = mapOf("key1" to "val1"))
+ assertThat(latest!!.getString("key1")).isEqualTo("val1")
+
+ sendDemoCommand(args = mapOf("key2" to "val2"))
+ assertThat(latest!!.getString("key2")).isEqualTo("val2")
+
+ job.cancel()
+ }
+
+ private fun sendDemoCommand(command: String? = TEST_COMMAND, args: Map<String, String>) {
+ val intent = Intent(ACTION_DEMO)
+ intent.putExtra("command", command)
+ args.forEach { arg -> intent.putExtra(arg.key, arg.value) }
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { it.onReceive(context, intent) }
+ }
+
+ companion object {
+ // Use a valid command until we properly fake out everything
+ const val TEST_COMMAND = COMMAND_STATUS
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
deleted file mode 100644
index 003efbf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.dreams
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class DreamCallbackControllerTest : SysuiTestCase() {
-
- @Mock private lateinit var callback: DreamCallbackController.DreamCallback
-
- private lateinit var underTest: DreamCallbackController
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- underTest = DreamCallbackController()
- }
-
- @Test
- fun testOnWakeUpInvokesCallback() {
- underTest.addCallback(callback)
- underTest.onWakeUp()
- verify(callback).onWakeUp()
-
- // Adding twice should not invoke twice
- reset(callback)
- underTest.addCallback(callback)
- underTest.onWakeUp()
- verify(callback, times(1)).onWakeUp()
-
- // After remove, no call to callback
- reset(callback)
- underTest.removeCallback(callback)
- underTest.onWakeUp()
- verify(callback, never()).onWakeUp()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
new file mode 100644
index 0000000..9f534ef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayCallbackControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var callback: DreamOverlayCallbackController.Callback
+
+ private lateinit var underTest: DreamOverlayCallbackController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = DreamOverlayCallbackController()
+ }
+
+ @Test
+ fun onWakeUpInvokesCallback() {
+ underTest.onStartDream()
+ assertThat(underTest.isDreaming).isEqualTo(true)
+
+ underTest.addCallback(callback)
+ underTest.onWakeUp()
+ verify(callback).onWakeUp()
+ assertThat(underTest.isDreaming).isEqualTo(false)
+
+ // Adding twice should not invoke twice
+ reset(callback)
+ underTest.addCallback(callback)
+ underTest.onWakeUp()
+ verify(callback, times(1)).onWakeUp()
+
+ // After remove, no call to callback
+ reset(callback)
+ underTest.removeCallback(callback)
+ underTest.onWakeUp()
+ verify(callback, never()).onWakeUp()
+ }
+
+ @Test
+ fun onStartDreamInvokesCallback() {
+ underTest.addCallback(callback)
+
+ assertThat(underTest.isDreaming).isEqualTo(false)
+
+ underTest.onStartDream()
+ verify(callback).onStartDream()
+ assertThat(underTest.isDreaming).isEqualTo(true)
+
+ // Adding twice should not invoke twice
+ reset(callback)
+ underTest.addCallback(callback)
+ underTest.onStartDream()
+ verify(callback, times(1)).onStartDream()
+
+ // After remove, no call to callback
+ reset(callback)
+ underTest.removeCallback(callback)
+ underTest.onStartDream()
+ verify(callback, never()).onStartDream()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 52663ce..8f97026 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -140,7 +140,7 @@
UiEventLogger mUiEventLogger;
@Mock
- DreamCallbackController mDreamCallbackController;
+ DreamOverlayCallbackController mDreamOverlayCallbackController;
@Captor
ArgumentCaptor<View> mViewCaptor;
@@ -186,7 +186,7 @@
mUiEventLogger,
mTouchInsetManager,
LOW_LIGHT_COMPONENT,
- mDreamCallbackController);
+ mDreamOverlayCallbackController);
}
@Test
@@ -398,7 +398,7 @@
mService.onWakeUp(callback);
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
- verify(mDreamCallbackController).onWakeUp();
+ verify(mDreamOverlayCallbackController).onWakeUp();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 563d44d3..be712f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -28,8 +28,7 @@
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
-import com.android.systemui.dreams.DreamCallbackController
-import com.android.systemui.dreams.DreamCallbackController.DreamCallback
+import com.android.systemui.dreams.DreamOverlayCallbackController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -68,7 +67,7 @@
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var dreamCallbackController: DreamCallbackController
+ @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -86,7 +85,7 @@
keyguardUpdateMonitor,
dozeTransitionListener,
authController,
- dreamCallbackController,
+ dreamOverlayCallbackController,
)
}
@@ -171,6 +170,29 @@
}
@Test
+ fun isKeyguardOccluded() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isOccluded).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardOccluded.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isOccluded).thenReturn(true)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isOccluded).thenReturn(false)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun isDozing() =
runTest(UnconfinedTestDispatcher()) {
var latest: Boolean? = null
@@ -343,19 +365,22 @@
}
@Test
- fun isDreamingFromDreamCallbackController() =
+ fun isDreamingFromDreamOverlayCallbackController() =
runTest(UnconfinedTestDispatcher()) {
- whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(true)
+ whenever(dreamOverlayCallbackController.isDreaming).thenReturn(false)
var latest: Boolean? = null
- val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+ val job = underTest.isDreamingWithOverlay.onEach { latest = it }.launchIn(this)
- assertThat(latest).isTrue()
+ assertThat(latest).isFalse()
val listener =
- withArgCaptor<DreamCallbackController.DreamCallback> {
- verify(dreamCallbackController).addCallback(capture())
+ withArgCaptor<DreamOverlayCallbackController.Callback> {
+ verify(dreamOverlayCallbackController).addCallback(capture())
}
+ listener.onStartDream()
+ assertThat(latest).isTrue()
+
listener.onWakeUp()
assertThat(latest).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a6cf840..d2b7838 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -23,6 +23,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -42,6 +44,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -64,8 +67,8 @@
// Used to verify transition requests for test output
@Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
- private lateinit var lockscreenBouncerTransitionInteractor:
- LockscreenBouncerTransitionInteractor
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
@Before
fun setUp() {
@@ -79,25 +82,82 @@
transitionRepository = KeyguardTransitionRepositoryImpl()
runner = KeyguardTransitionRunner(transitionRepository)
- lockscreenBouncerTransitionInteractor =
- LockscreenBouncerTransitionInteractor(
+ fromLockscreenTransitionInteractor =
+ FromLockscreenTransitionInteractor(
scope = testScope,
keyguardInteractor = KeyguardInteractor(keyguardRepository),
shadeRepository = shadeRepository,
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
- lockscreenBouncerTransitionInteractor.start()
+ fromLockscreenTransitionInteractor.start()
+
+ fromDreamingTransitionInteractor =
+ FromDreamingTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = KeyguardInteractor(keyguardRepository),
+ keyguardTransitionRepository = mockTransitionRepository,
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ )
+ fromDreamingTransitionInteractor.start()
}
@Test
+ fun `DREAMING to LOCKSCREEN`() =
+ testScope.runTest {
+ // GIVEN a device is dreaming and occluded
+ keyguardRepository.setDreamingWithOverlay(true)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to DREAMING
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN doze is complete
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ // AND dreaming has stopped
+ keyguardRepository.setDreamingWithOverlay(false)
+ // AND occluded has stopped
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to BOUNCER should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun `LOCKSCREEN to BOUNCER via bouncer showing call`() =
testScope.runTest {
// GIVEN a device that has at least woken up
keyguardRepository.setWakefulnessModel(startingToWake())
runCurrent()
- // GIVEN a transition has run to LOCKSCREEN
+ // GIVEN a prior transition has run to LOCKSCREEN
runner.startTransition(
testScope,
TransitionInfo(
@@ -122,7 +182,7 @@
verify(mockTransitionRepository).startTransition(capture())
}
// THEN a transition to BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("LockscreenBouncerTransitionInteractor")
+ assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
assertThat(info.to).isEqualTo(KeyguardState.BOUNCER)
assertThat(info.animator).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c54e456..5571663 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.DreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
new file mode 100644
index 0000000..db6fc13
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Translates items away/towards the hinge when the device is opened/closed. This is controlled by
+ * the set of ids, which also dictact which direction to move and when, via a filter fn.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationPanelUnfoldAnimationControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+
+ @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+
+ @Mock private lateinit var parent: ViewGroup
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+ private lateinit var underTest: NotificationPanelUnfoldAnimationController
+ private lateinit var progressListener: TransitionProgressListener
+ private var xTranslationMax = 0f
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ xTranslationMax =
+ context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
+
+ underTest =
+ NotificationPanelUnfoldAnimationController(
+ context,
+ statusBarStateController,
+ progressProvider
+ )
+ underTest.setup(parent)
+
+ verify(progressProvider).addCallback(capture(progressListenerCaptor))
+ progressListener = progressListenerCaptor.value
+ }
+
+ @Test
+ fun whenInKeyguardState_viewDoesNotMove() {
+ whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+
+ val view = View(context)
+ whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+ progressListener.onTransitionStarted()
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionProgress(0f)
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionProgress(0.5f)
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionFinished()
+ assertThat(view.translationX).isZero()
+ }
+
+ @Test
+ fun whenInShadeState_viewDoesMove() {
+ whenever(statusBarStateController.getState()).thenReturn(SHADE)
+
+ val view = View(context)
+ whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+ progressListener.onTransitionStarted()
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionProgress(0f)
+ assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+ progressListener.onTransitionProgress(0.5f)
+ assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+ progressListener.onTransitionFinished()
+ assertThat(view.translationX).isZero()
+ }
+
+ @Test
+ fun whenInShadeLockedState_viewDoesMove() {
+ whenever(statusBarStateController.getState()).thenReturn(SHADE_LOCKED)
+
+ val view = View(context)
+ whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+ progressListener.onTransitionStarted()
+ assertThat(view.translationX).isZero()
+
+ progressListener.onTransitionProgress(0f)
+ assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+ progressListener.onTransitionProgress(0.5f)
+ assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+ progressListener.onTransitionFinished()
+ assertThat(view.translationX).isZero()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 43c6942..3e769e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -21,6 +21,7 @@
import android.provider.Settings.Secure.DOZE_TAP_SCREEN_GESTURE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.os.PowerManager
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -36,9 +37,9 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.ArgumentMatchers.anyObject
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.never
@@ -106,7 +107,8 @@
underTest.onSingleTapUp(upEv)
// THEN wake up device if dozing
- verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+ verify(centralSurfaces).wakeUpIfDozing(
+ anyLong(), any(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
}
@Test
@@ -125,7 +127,8 @@
underTest.onDoubleTapEvent(upEv)
// THEN wake up device if dozing
- verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+ verify(centralSurfaces).wakeUpIfDozing(
+ anyLong(), any(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
}
@Test
@@ -156,7 +159,8 @@
underTest.onSingleTapUp(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+ verify(centralSurfaces, never()).wakeUpIfDozing(
+ anyLong(), any(), anyString(), anyInt())
}
@Test
@@ -203,7 +207,8 @@
underTest.onDoubleTapEvent(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+ verify(centralSurfaces, never()).wakeUpIfDozing(
+ anyLong(), any(), anyString(), anyInt())
}
@Test
@@ -222,7 +227,8 @@
underTest.onSingleTapUp(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+ verify(centralSurfaces, never()).wakeUpIfDozing(
+ anyLong(), any(), anyString(), anyInt())
}
@Test
@@ -241,7 +247,8 @@
underTest.onDoubleTapEvent(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+ verify(centralSurfaces, never()).wakeUpIfDozing(
+ anyLong(), any(), anyString(), anyInt())
}
fun updateSettings() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 1ce460c..3a1f9b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -31,6 +31,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -177,6 +178,8 @@
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -189,8 +192,6 @@
import java.io.PrintWriter;
import java.util.Optional;
-import dagger.Lazy;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -306,6 +307,7 @@
@Mock private ViewRootImpl mViewRootImpl;
@Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+ @Mock IPowerManager mPowerManagerService;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -319,9 +321,8 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- IPowerManager powerManagerService = mock(IPowerManager.class);
IThermalService thermalService = mock(IThermalService.class);
- mPowerManager = new PowerManager(mContext, powerManagerService, thermalService,
+ mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
Handler.createAsync(Looper.myLooper()));
mNotificationInterruptStateProvider =
@@ -363,7 +364,7 @@
when(mStackScrollerController.getView()).thenReturn(mStackScroller);
when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
- when(powerManagerService.isInteractive()).thenReturn(true);
+ when(mPowerManagerService.isInteractive()).thenReturn(true);
when(mStackScroller.getActivatedChild()).thenReturn(null);
doAnswer(invocation -> {
@@ -1190,6 +1191,34 @@
verify(mStatusBarStateController).setState(SHADE);
}
+ @Test
+ public void dozing_wakeUp() throws RemoteException {
+ // GIVEN can wakeup when dozing & is dozing
+ when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
+ setDozing(true);
+
+ // WHEN wakeup is requested
+ final int wakeReason = PowerManager.WAKE_REASON_TAP;
+ mCentralSurfaces.wakeUpIfDozing(0, null, "", wakeReason);
+
+ // THEN power manager receives wakeup
+ verify(mPowerManagerService).wakeUp(eq(0L), eq(wakeReason), anyString(), anyString());
+ }
+
+ @Test
+ public void notDozing_noWakeUp() throws RemoteException {
+ // GIVEN can wakeup when dozing and NOT dozing
+ when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
+ setDozing(false);
+
+ // WHEN wakeup is requested
+ final int wakeReason = PowerManager.WAKE_REASON_TAP;
+ mCentralSurfaces.wakeUpIfDozing(0, null, "", wakeReason);
+
+ // THEN power manager receives wakeup
+ verify(mPowerManagerService, never()).wakeUp(anyLong(), anyInt(), anyString(), anyString());
+ }
+
/**
* Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
* to reconfigure the keyguard to reflect the requested showing/occluded states.
@@ -1226,6 +1255,13 @@
states);
}
+ private void setDozing(boolean isDozing) {
+ ArgumentCaptor<StatusBarStateController.StateListener> callbackCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(callbackCaptor.capture(), anyInt());
+ callbackCaptor.getValue().onDozingChanged(isDozing);
+ }
+
public static class TestableNotificationInterruptStateProviderImpl extends
NotificationInterruptStateProviderImpl {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
new file mode 100644
index 0000000..f822ba0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class MobileConnectionModelTest : SysuiTestCase() {
+
+ @Test
+ fun `log diff - initial log contains all columns`() {
+ val logger = TestLogger()
+ val connection = MobileConnectionModel()
+
+ connection.logFull(logger)
+
+ assertThat(logger.changes)
+ .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString()))
+ assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString()))
+ assertThat(logger.changes)
+ .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString()))
+ assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString()))
+ assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString()))
+ assertThat(logger.changes)
+ .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString()))
+ assertThat(logger.changes)
+ .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
+ assertThat(logger.changes)
+ .contains(Pair(COL_ACTIVITY_DIRECTION, connection.dataActivityDirection.toString()))
+ assertThat(logger.changes)
+ .contains(
+ Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
+ )
+ assertThat(logger.changes)
+ .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString()))
+ }
+
+ @Test
+ fun `log diff - primary level changes - only level is logged`() {
+ val logger = TestLogger()
+ val connectionOld = MobileConnectionModel(primaryLevel = 1)
+
+ val connectionNew = MobileConnectionModel(primaryLevel = 2)
+
+ connectionNew.logDiffs(connectionOld, logger)
+
+ assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2")))
+ }
+
+ private class TestLogger : TableRowLogger {
+ val changes = mutableListOf<Pair<String, String>>()
+
+ override fun logChange(columnName: String, value: String?) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+
+ override fun logChange(columnName: String, value: Int) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+
+ override fun logChange(columnName: String, value: Boolean) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 59eec53..d6a9ee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
-class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+class FakeMobileConnectionRepository(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepository {
private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
override val connectionInfo = _connectionInfo
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 04d3cdd..7f93328 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -22,14 +22,17 @@
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
-class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
- MobileConnectionsRepository {
+class FakeMobileConnectionsRepository(
+ mobileMappings: MobileMappingsProxy,
+ val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionsRepository {
val GSM_KEY = mobileMappings.toIconKey(GSM)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val UMTS_KEY = mobileMappings.toIconKey(UMTS)
@@ -63,7 +66,7 @@
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId]
- ?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it }
+ ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
}
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 18ae90d..5d377a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -24,6 +24,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
@@ -37,6 +39,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -69,12 +72,14 @@
private lateinit var realRepo: MobileConnectionsRepositoryImpl
private lateinit var demoRepo: DemoMobileConnectionsRepository
private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+ private lateinit var logFactory: TableLogBufferFactory
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var dumpManager: DumpManager
private val globalSettings = FakeSettings()
private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
@@ -86,6 +91,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
+
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
@@ -114,6 +121,7 @@
dataSource = mockDataSource,
scope = scope,
context = context,
+ logFactory = logFactory,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 3d5316d..2102085 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -30,6 +31,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
@@ -54,6 +56,9 @@
@RunWith(Parameterized::class)
internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
SysuiTestCase() {
+
+ private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -76,6 +81,7 @@
dataSource = mockDataSource,
scope = testScope.backgroundScope,
context = context,
+ logFactory = logFactory,
)
connectionsRepo.startProcessingCommands()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 34f30eb..cdbe75e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -23,6 +23,8 @@
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -32,6 +34,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,6 +50,9 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
+ private val dumpManager: DumpManager = mock()
+ private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
+
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -68,6 +74,7 @@
dataSource = mockDataSource,
scope = testScope.backgroundScope,
context = context,
+ logFactory = logFactory,
)
underTest.startProcessingCommands()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 7fa8065..7970443 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -50,6 +50,7 @@
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -87,14 +88,15 @@
@SmallTest
class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
+ private lateinit var connectionsRepo: FakeMobileConnectionsRepository
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogger: TableLogBuffer
private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy()
private val globalSettings = FakeSettings()
- private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
@Before
fun setUp() {
@@ -102,6 +104,8 @@
globalSettings.userId = UserHandle.USER_ALL
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
+ connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
+
underTest =
MobileConnectionRepositoryImpl(
context,
@@ -116,6 +120,7 @@
mobileMappings,
IMMEDIATE,
logger,
+ tableLogger,
scope,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 3cc1e8b..b8cd7a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -34,12 +34,15 @@
import com.android.settingslib.R
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
@@ -57,6 +60,7 @@
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -72,6 +76,7 @@
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var logBufferFactory: TableLogBufferFactory
private val mobileMappings = FakeMobileMappingsProxy()
@@ -89,6 +94,10 @@
}
}
+ whenever(logBufferFactory.create(anyString(), anyInt())).thenAnswer { _ ->
+ mock<TableLogBuffer>()
+ }
+
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
fakeBroadcastDispatcher,
@@ -99,6 +108,7 @@
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = scope,
+ logFactory = logBufferFactory,
)
underTest =
@@ -271,6 +281,32 @@
}
@Test
+ fun `connection repository - log buffer contains sub id in its name`() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptions.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Get repos to trigger creation
+ underTest.getRepoForSubId(SUB_1_ID)
+ verify(logBufferFactory)
+ .create(
+ eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)),
+ anyInt(),
+ )
+ underTest.getRepoForSubId(SUB_2_ID)
+ verify(logBufferFactory)
+ .create(
+ eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)),
+ anyInt(),
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun testDefaultDataSubId_updatesOnBroadcast() =
runBlocking(IMMEDIATE) {
var latest: Int? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index c3519b7..c494589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -19,11 +19,14 @@
import android.telephony.CellSignalStrength
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileIconInteractor : MobileIconInteractor {
+class FakeMobileIconInteractor(
+ override val tableLogBuffer: TableLogBuffer,
+) : MobileIconInteractor {
override val alwaysShowDataRatIcon = MutableStateFlow(false)
override val activity =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 9f300e9..19e5516 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -22,12 +22,15 @@
import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
+class FakeMobileIconsInteractor(
+ mobileMappings: MobileMappingsProxy,
+ val tableLogBuffer: TableLogBuffer,
+) : MobileIconsInteractor {
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -48,8 +51,7 @@
override val isDefaultConnectionFailed = MutableStateFlow(false)
- private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
- override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
+ override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
@@ -67,7 +69,7 @@
/** Always returns a new fake interactor */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
- return FakeMobileIconInteractor()
+ return FakeMobileIconInteractor(tableLogBuffer)
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 4dca780..83c5055 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -49,8 +49,8 @@
class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
private val mobileMappingsProxy = FakeMobileMappingsProxy()
- private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
- private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID)
+ private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+ private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
private val scope = CoroutineScope(IMMEDIATE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 8557894..2fa3467 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
@@ -28,6 +29,7 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -44,9 +46,9 @@
@SmallTest
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
+ private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private val userSetupRepository = FakeUserSetupRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
- private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -55,6 +57,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
connectionsRepository.setMobileConnectionRepositoryMap(
mapOf(
SUB_1_ID to CONNECTION_1,
@@ -290,21 +293,23 @@
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
+ private val tableLogBuffer =
+ TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
private const val SUB_1_ID = 1
private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
- private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID)
+ private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
private const val SUB_2_ID = 2
private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
- private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID)
+ private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer)
private const val SUB_3_ID = 3
private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
- private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID)
+ private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer)
private const val SUB_4_ID = 4
private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
- private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID)
+ private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
new file mode 100644
index 0000000..043d55a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
+ private lateinit var commonImpl: MobileIconViewModelCommon
+ private lateinit var homeIcon: HomeMobileIconViewModel
+ private lateinit var qsIcon: QsMobileIconViewModel
+ private lateinit var keyguardIcon: KeyguardMobileIconViewModel
+ private lateinit var interactor: FakeMobileIconInteractor
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var constants: ConnectivityConstants
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ interactor = FakeMobileIconInteractor(tableLogBuffer)
+ interactor.apply {
+ setLevel(1)
+ setIsDefaultDataEnabled(true)
+ setIsFailedConnection(false)
+ setIconGroup(TelephonyIcons.THREE_G)
+ setIsEmergencyOnly(false)
+ setNumberOfLevels(4)
+ isDataConnected.value = true
+ }
+ commonImpl =
+ MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+
+ homeIcon = HomeMobileIconViewModel(commonImpl, logger)
+ qsIcon = QsMobileIconViewModel(commonImpl, logger)
+ keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger)
+ }
+
+ @Test
+ fun `location based view models receive same icon id when common impl updates`() =
+ testScope.runTest {
+ var latestHome: Int? = null
+ val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this)
+
+ var latestQs: Int? = null
+ val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this)
+
+ var latestKeyguard: Int? = null
+ val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this)
+
+ var expected = defaultSignal(level = 1)
+
+ assertThat(latestHome).isEqualTo(expected)
+ assertThat(latestQs).isEqualTo(expected)
+ assertThat(latestKeyguard).isEqualTo(expected)
+
+ interactor.setLevel(2)
+ expected = defaultSignal(level = 2)
+
+ assertThat(latestHome).isEqualTo(expected)
+ assertThat(latestQs).isEqualTo(expected)
+ assertThat(latestKeyguard).isEqualTo(expected)
+
+ homeJob.cancel()
+ qsJob.cancel()
+ keyguardJob.cancel()
+ }
+
+ companion object {
+ private const val SUB_1_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 415ce75..50221bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -22,32 +22,42 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileIconViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconViewModel
- private val interactor = FakeMobileIconInteractor()
+ private lateinit var interactor: FakeMobileIconInteractor
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply {
setLevel(1)
setIsDefaultDataEnabled(true)
@@ -57,12 +67,13 @@
setNumberOfLevels(4)
isDataConnected.value = true
}
- underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+ underTest =
+ MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
}
@Test
fun iconId_correctLevel_notCutout() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: Int? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
val expected = defaultSignal()
@@ -74,7 +85,7 @@
@Test
fun iconId_cutout_whenDefaultDataDisabled() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
interactor.setIsDefaultDataEnabled(false)
var latest: Int? = null
@@ -88,7 +99,7 @@
@Test
fun networkType_dataEnabled_groupIsRepresented() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val expected =
Icon.Resource(
THREE_G.dataType,
@@ -106,7 +117,7 @@
@Test
fun networkType_nullWhenDisabled() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(false)
var latest: Icon? = null
@@ -119,7 +130,7 @@
@Test
fun networkType_nullWhenFailedConnection() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true)
interactor.setIsFailedConnection(true)
@@ -133,7 +144,7 @@
@Test
fun networkType_nullWhenDataDisconnects() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val initial =
Icon.Resource(
THREE_G.dataType,
@@ -157,7 +168,7 @@
@Test
fun networkType_null_changeToDisabled() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val expected =
Icon.Resource(
THREE_G.dataType,
@@ -180,7 +191,7 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisabled() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true)
interactor.alwaysShowDataRatIcon.value = true
@@ -200,7 +211,7 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisconnected() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.isDataConnected.value = false
interactor.alwaysShowDataRatIcon.value = true
@@ -220,7 +231,7 @@
@Test
fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsFailedConnection(true)
interactor.alwaysShowDataRatIcon.value = true
@@ -240,7 +251,7 @@
@Test
fun roaming() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
interactor.isRoaming.value = true
var latest: Boolean? = null
val job = underTest.roaming.onEach { latest = it }.launchIn(this)
@@ -256,10 +267,17 @@
@Test
fun `data activity - null when config is off`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(false)
- underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+ underTest =
+ MobileIconViewModel(
+ SUB_1_ID,
+ interactor,
+ logger,
+ constants,
+ testScope.backgroundScope,
+ )
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -288,10 +306,17 @@
@Test
fun `data activity - config on - test indicators`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
- underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
+ underTest =
+ MobileIconViewModel(
+ SUB_1_ID,
+ interactor,
+ logger,
+ constants,
+ testScope.backgroundScope,
+ )
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -340,16 +365,15 @@
containerJob.cancel()
}
- /** Convenience constructor for these tests */
- private fun defaultSignal(
- level: Int = 1,
- connected: Boolean = true,
- ): Int {
- return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
- }
-
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
+
+ /** Convenience constructor for these tests */
+ fun defaultSignal(
+ level: Int = 1,
+ connected: Boolean = true,
+ ): Int {
+ return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
new file mode 100644
index 0000000..d6cb762
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileIconsViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconsViewModel
+ private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var constants: ConnectivityConstants
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ val subscriptionIdsFlow =
+ interactor.filteredSubscriptions
+ .map { subs -> subs.map { it.subscriptionId } }
+ .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
+
+ underTest =
+ MobileIconsViewModel(
+ subscriptionIdsFlow,
+ interactor,
+ logger,
+ constants,
+ testScope.backgroundScope,
+ )
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ }
+
+ @Test
+ fun `caching - mobile icon view model is reused for same sub id`() =
+ testScope.runTest {
+ val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+ val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS)
+
+ assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl)
+ }
+
+ @Test
+ fun `caching - invalid view models are removed from cache when sub disappears`() =
+ testScope.runTest {
+ // Retrieve models to trigger caching
+ val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+ val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+
+ // Both impls are cached
+ assertThat(underTest.mobileIconSubIdCache)
+ .containsExactly(1, model1.commonImpl, 2, model2.commonImpl)
+
+ // SUB_1 is removed from the list...
+ interactor.filteredSubscriptions.value = listOf(SUB_2)
+
+ // ... and dropped from the cache
+ assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
+ }
+
+ companion object {
+ private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false)
+ private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
new file mode 100644
index 0000000..b935442
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository
+
+import android.net.ConnectivityManager
+import android.net.wifi.WifiManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiRepositorySwitcherTest : SysuiTestCase() {
+ private lateinit var underTest: WifiRepositorySwitcher
+ private lateinit var realImpl: WifiRepositoryImpl
+ private lateinit var demoImpl: DemoWifiRepository
+
+ @Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogger: TableLogBuffer
+ @Mock private lateinit var connectivityManager: ConnectivityManager
+ @Mock private lateinit var wifiManager: WifiManager
+ @Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
+ private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
+
+ private val mainExecutor = FakeExecutor(FakeSystemClock())
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // Never start in demo mode
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+ realImpl =
+ WifiRepositoryImpl(
+ fakeBroadcastDispatcher,
+ connectivityManager,
+ logger,
+ tableLogger,
+ mainExecutor,
+ testScope.backgroundScope,
+ wifiManager,
+ )
+
+ whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
+
+ demoImpl =
+ DemoWifiRepository(
+ demoModeWifiDataSource,
+ testScope.backgroundScope,
+ )
+
+ underTest =
+ WifiRepositorySwitcher(
+ realImpl,
+ demoImpl,
+ demoModeController,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun `switcher active repo - updates when demo mode changes`() =
+ testScope.runTest {
+ assertThat(underTest.activeRepo.value).isSameInstanceAs(realImpl)
+
+ var latest: WifiRepository? = null
+ val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
+
+ startDemoMode()
+
+ assertThat(latest).isSameInstanceAs(demoImpl)
+
+ finishDemoMode()
+
+ assertThat(latest).isSameInstanceAs(realImpl)
+
+ job.cancel()
+ }
+
+ private fun startDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(true)
+ getDemoModeCallback().onDemoModeStarted()
+ }
+
+ private fun finishDemoMode() {
+ whenever(demoModeController.isInDemoMode).thenReturn(false)
+ getDemoModeCallback().onDemoModeFinished()
+ }
+
+ private fun getDemoModeCallback(): DemoMode {
+ val captor = kotlinArgumentCaptor<DemoMode>()
+ Mockito.verify(demoModeController).addCallback(captor.capture())
+ return captor.value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index b47f177..4158434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -146,7 +146,7 @@
@Test
fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -183,7 +183,7 @@
@Test
fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -225,7 +225,7 @@
@Test
fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
@@ -268,7 +268,7 @@
@Test
fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -308,7 +308,7 @@
@Test
fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -330,7 +330,7 @@
@Test
fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -352,7 +352,7 @@
@Test
fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -374,7 +374,7 @@
@Test
fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -396,7 +396,7 @@
@Test
fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -418,7 +418,7 @@
@Test
fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -440,7 +440,7 @@
@Test
fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -462,7 +462,7 @@
@Test
fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 2c47204..4b32ee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -55,6 +55,7 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
@@ -414,7 +415,9 @@
mDependency,
TestableLooper.get(this));
ExpandableNotificationRow row = helper.createRow();
+ FrameLayout remoteInputViewParent = new FrameLayout(mContext);
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ remoteInputViewParent.addView(view);
bindController(view, row.getEntry());
// Start defocus animation
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5501949..39d2eca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -46,12 +46,18 @@
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
+ private val _isKeyguardOccluded = MutableStateFlow(false)
+ override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
+
private val _isDozing = MutableStateFlow(false)
override val isDozing: Flow<Boolean> = _isDozing
private val _isDreaming = MutableStateFlow(false)
override val isDreaming: Flow<Boolean> = _isDreaming
+ private val _isDreamingWithOverlay = MutableStateFlow(false)
+ override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
+
private val _dozeAmount = MutableStateFlow(0f)
override val linearDozeAmount: Flow<Float> = _dozeAmount
@@ -112,10 +118,18 @@
_isKeyguardShowing.value = isShowing
}
+ fun setKeyguardOccluded(isOccluded: Boolean) {
+ _isKeyguardOccluded.value = isOccluded
+ }
+
fun setDozing(isDozing: Boolean) {
_isDozing.value = isDozing
}
+ fun setDreamingWithOverlay(isDreaming: Boolean) {
+ _isDreamingWithOverlay.value = isDreaming
+ }
+
fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount
}
@@ -144,6 +158,10 @@
_fingerprintSensorLocation.tryEmit(location)
}
+ fun setDozeTransitionModel(model: DozeTransitionModel) {
+ _dozeTransitionModel.value = model
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
diff --git a/services/api/current.txt b/services/api/current.txt
index b5798d5..090a449 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -72,16 +72,90 @@
package com.android.server.pm.pkg {
public interface AndroidPackage {
+ method @Nullable public String getAppComponentFactory();
+ method @Nullable public String getApplicationClassName();
+ method @Nullable public String getBackupAgentName();
+ method @DrawableRes public int getBannerRes();
+ method public int getBaseRevisionCode();
+ method public int getCategory();
+ method @Nullable public String getClassLoaderName();
+ method @Dimension(unit=android.annotation.Dimension.DP) public int getCompatibleWidthLimitDp();
+ method @XmlRes public int getDataExtractionRulesRes();
+ method @StringRes public int getDescriptionRes();
+ method @XmlRes public int getFullBackupContentRes();
+ method public int getGwpAsanMode();
+ method @DrawableRes public int getIconRes();
+ method @StringRes public int getLabelRes();
+ method @Dimension(unit=android.annotation.Dimension.DP) public int getLargestWidthLimitDp();
method @NonNull public java.util.List<java.lang.String> getLibraryNames();
+ method @XmlRes public int getLocaleConfigRes();
+ method @DrawableRes public int getLogoRes();
+ method public long getLongVersionCode();
+ method public float getMaxAspectRatio();
+ method public float getMinAspectRatio();
+ method public int getNativeHeapZeroInitialized();
+ method @XmlRes public int getNetworkSecurityConfigRes();
+ method @Nullable public String getRequiredAccountType();
+ method @Dimension(unit=android.annotation.Dimension.DP) public int getRequiresSmallestWidthDp();
+ method @Nullable public String getRestrictedAccountType();
+ method @DrawableRes public int getRoundIconRes();
method @Nullable public String getSdkLibraryName();
+ method @Nullable public String getSharedUserId();
+ method @StringRes public int getSharedUserLabelRes();
method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
method @Nullable public String getStaticSharedLibraryName();
method @NonNull public java.util.UUID getStorageUuid();
method public int getTargetSdkVersion();
+ method @StyleRes public int getThemeRes();
+ method public int getUiOptions();
+ method @Nullable public String getVersionName();
+ method @Nullable public String getZygotePreloadName();
+ method public boolean isAllowAudioPlaybackCapture();
+ method public boolean isAllowBackup();
+ method public boolean isAllowClearUserData();
+ method public boolean isAllowClearUserDataOnFailedRestore();
+ method public boolean isAllowNativeHeapPointerTagging();
+ method public boolean isAllowTaskReparenting();
+ method public boolean isAnyDensity();
+ method public boolean isAttributionsUserVisible();
+ method public boolean isBackupInForeground();
+ method public boolean isCantSaveState();
+ method public boolean isCoreApp();
+ method public boolean isCrossProfile();
method public boolean isDebuggable();
+ method public boolean isDefaultToDeviceProtectedStorage();
+ method public boolean isDirectBootAware();
+ method public boolean isExtractNativeLibs();
+ method public boolean isFactoryTest();
+ method public boolean isForceQueryable();
+ method public boolean isFullBackupOnly();
+ method public boolean isHardwareAccelerated();
+ method public boolean isHasCode();
+ method public boolean isHasFragileUserData();
method public boolean isIsolatedSplitLoading();
+ method public boolean isKillAfterRestore();
+ method public boolean isLargeHeap();
+ method public boolean isLeavingSharedUser();
+ method public boolean isMultiArch();
+ method public boolean isNativeLibraryRootRequiresIsa();
+ method public boolean isOnBackInvokedCallbackEnabled();
+ method public boolean isPersistent();
+ method public boolean isProfileable();
+ method public boolean isProfileableByShell();
+ method public boolean isRequestLegacyExternalStorage();
+ method public boolean isRequiredForAllUsers();
+ method public boolean isResetEnabledSettingsOnAppDataCleared();
+ method public boolean isRestoreAnyVersion();
method public boolean isSignedWithPlatformKey();
+ method public boolean isSupportsExtraLargeScreens();
+ method public boolean isSupportsLargeScreens();
+ method public boolean isSupportsNormalScreens();
+ method public boolean isSupportsRtl();
+ method public boolean isSupportsSmallScreens();
+ method public boolean isTestOnly();
+ method public boolean isUse32BitAbi();
method public boolean isUseEmbeddedDex();
+ method public boolean isUsesCleartextTraffic();
method public boolean isUsesNonSdkApi();
method public boolean isVmSafeMode();
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 1c571a7..53f5fe1 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,6 +25,8 @@
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
@@ -1556,6 +1558,22 @@
}
}
+ public void reportDelayedRestoreResult(String packageName, List<DataTypeResult> results) {
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+ if (!isUserReadyForBackup(userId)) {
+ Slog.w(TAG, "Returning from reportDelayedRestoreResult as backup for user" + userId +
+ " is not initialized yet");
+ return;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId,
+ /* caller */ "reportDelayedRestoreResult()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.reportDelayedRestoreResult(packageName, results);
+ }
+ }
+
/**
* Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
* If the user is not registered with the service (either the user is locked or not eligible for
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ce3e628..6ba01d7 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -49,6 +49,7 @@
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -505,13 +506,14 @@
@VisibleForTesting
UserBackupManagerService(Context context, PackageManager packageManager,
- LifecycleOperationStorage operationStorage) {
+ LifecycleOperationStorage operationStorage, TransportManager transportManager) {
mContext = context;
mUserId = 0;
mRegisterTransportsRequestedTime = 0;
mPackageManager = packageManager;
mOperationStorage = operationStorage;
+ mTransportManager = transportManager;
mBaseStateDir = null;
mDataDir = null;
@@ -521,7 +523,6 @@
mRunInitReceiver = null;
mRunInitIntent = null;
mAgentTimeoutParameters = null;
- mTransportManager = null;
mActivityManagerInternal = null;
mAlarmManager = null;
mConstants = null;
@@ -3038,6 +3039,37 @@
mBackupPreferences.addExcludedKeys(packageName, keys);
}
+ public void reportDelayedRestoreResult(String packageName,
+ List<BackupRestoreEventLogger.DataTypeResult> results) {
+ String transport = mTransportManager.getCurrentTransportName();
+ if (transport == null) {
+ Slog.w(TAG, "Failed to send delayed restore logs as no transport selected");
+ return;
+ }
+
+ TransportConnection transportConnection = null;
+ try {
+ PackageInfo packageInfo = getPackageManager().getPackageInfoAsUser(packageName,
+ PackageManager.PackageInfoFlags.of(/* value */ 0), getUserId());
+
+ transportConnection = mTransportManager.getTransportClientOrThrow(
+ transport, /* caller */"BMS.reportDelayedRestoreResult");
+ BackupTransportClient transportClient = transportConnection.connectOrThrow(
+ /* caller */ "BMS.reportDelayedRestoreResult");
+
+ IBackupManagerMonitor monitor = transportClient.getBackupManagerMonitor();
+ BackupManagerMonitorUtils.sendAgentLoggingResults(monitor, packageInfo, results);
+ } catch (NameNotFoundException | TransportNotAvailableException
+ | TransportNotRegisteredException | RemoteException e) {
+ Slog.w(TAG, "Failed to send delayed restore logs: " + e);
+ } finally {
+ if (transportConnection != null) {
+ mTransportManager.disposeOfTransportClient(transportConnection,
+ /* caller */"BMS.reportDelayedRestoreResult");
+ }
+ }
+ }
+
private boolean startConfirmationUi(int token, String action) {
try {
Intent confIntent = new Intent(action);
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
index 8eda5b9..57ad89b 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
@@ -24,10 +24,11 @@
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.backup.BackupManagerMonitor;
-import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManagerMonitor;
import android.content.pm.PackageInfo;
import android.os.Bundle;
@@ -119,19 +120,11 @@
}
try {
- AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture =
+ AndroidFuture<List<DataTypeResult>> resultsFuture =
new AndroidFuture<>();
agent.getLoggerResults(resultsFuture);
- Bundle loggerResultsBundle = new Bundle();
- loggerResultsBundle.putParcelableList(
- EXTRA_LOG_AGENT_LOGGING_RESULTS,
+ return sendAgentLoggingResults(monitor, pkg,
resultsFuture.get(AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
- return BackupManagerMonitorUtils.monitorEvent(
- monitor,
- LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
- pkg,
- LOG_EVENT_CATEGORY_AGENT,
- loggerResultsBundle);
} catch (TimeoutException e) {
Slog.w(TAG, "Timeout while waiting to retrieve logging results from agent", e);
} catch (Exception e) {
@@ -140,6 +133,19 @@
return monitor;
}
+ public static IBackupManagerMonitor sendAgentLoggingResults(
+ @NonNull IBackupManagerMonitor monitor, PackageInfo pkg, List<DataTypeResult> results) {
+ Bundle loggerResultsBundle = new Bundle();
+ loggerResultsBundle.putParcelableList(
+ EXTRA_LOG_AGENT_LOGGING_RESULTS, results);
+ return monitorEvent(
+ monitor,
+ LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
+ pkg,
+ LOG_EVENT_CATEGORY_AGENT,
+ loggerResultsBundle);
+ }
+
/**
* Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will
* be created.
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 9922818..7b8ca91 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -493,9 +493,7 @@
private boolean isLocationPermissionRequired(Set<Integer> events) {
return events.contains(TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)
- || events.contains(TelephonyCallback.EVENT_CELL_INFO_CHANGED)
- || events.contains(TelephonyCallback.EVENT_REGISTRATION_FAILURE)
- || events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED);
+ || events.contains(TelephonyCallback.EVENT_CELL_INFO_CHANGED);
}
private boolean isPhoneStatePermissionRequired(Set<Integer> events, String callingPackage,
@@ -1002,6 +1000,10 @@
@Override
public void notifySubscriptionInfoChanged() {
if (VDBG) log("notifySubscriptionInfoChanged:");
+ if (!checkNotifyPermission("notifySubscriptionInfoChanged()")) {
+ return;
+ }
+
synchronized (mRecords) {
if (!mHasNotifySubscriptionInfoChangedOccurred) {
log("notifySubscriptionInfoChanged: first invocation mRecords.size="
@@ -1028,6 +1030,10 @@
@Override
public void notifyOpportunisticSubscriptionInfoChanged() {
if (VDBG) log("notifyOpptSubscriptionInfoChanged:");
+ if (!checkNotifyPermission("notifyOpportunisticSubscriptionInfoChanged()")) {
+ return;
+ }
+
synchronized (mRecords) {
if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
log("notifyOpptSubscriptionInfoChanged: first invocation mRecords.size="
@@ -1359,15 +1365,19 @@
}
if (events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED)) {
BarringInfo barringInfo = mBarringInfo.get(r.phoneId);
- BarringInfo biNoLocation = barringInfo != null
- ? barringInfo.createLocationInfoSanitizedCopy() : null;
- if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
- try {
- r.callback.onBarringInfoChanged(
- checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
- ? barringInfo : biNoLocation);
- } catch (RemoteException ex) {
- remove(r.binder);
+ if (VDBG) {
+ log("listen: call onBarringInfoChanged=" + barringInfo);
+ }
+ if (barringInfo != null) {
+ BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
+
+ try {
+ r.callback.onBarringInfoChanged(
+ checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
+ ? barringInfo : biNoLocation);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
}
if (events.contains(
@@ -3618,29 +3628,21 @@
private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage,
@Nullable String callingFeatureId, String message) {
- LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
- new LocationAccessPolicy.LocationPermissionQuery.Builder()
- .setCallingPackage(callingPackage)
- .setCallingFeatureId(callingFeatureId)
- .setMethod(message + " events: " + events)
- .setCallingPid(Binder.getCallingPid())
- .setCallingUid(Binder.getCallingUid());
-
- boolean shouldCheckLocationPermissions = false;
-
+ boolean isPermissionCheckSuccessful = true;
if (isLocationPermissionRequired(events)) {
+ LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
+ new LocationAccessPolicy.LocationPermissionQuery.Builder()
+ .setCallingPackage(callingPackage)
+ .setCallingFeatureId(callingFeatureId)
+ .setMethod(message + " events: " + events)
+ .setCallingPid(Binder.getCallingPid())
+ .setCallingUid(Binder.getCallingUid());
// Everything that requires fine location started in Q. So far...
locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q);
// If we're enforcing fine starting in Q, we also want to enforce coarse even for
// older SDK versions.
locationQueryBuilder.setMinSdkVersionForCoarse(0);
locationQueryBuilder.setMinSdkVersionForEnforcement(0);
- shouldCheckLocationPermissions = true;
- }
-
- boolean isPermissionCheckSuccessful = true;
-
- if (shouldCheckLocationPermissions) {
LocationAccessPolicy.LocationPermissionResult result =
LocationAccessPolicy.checkLocationPermission(
mContext, locationQueryBuilder.build());
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 45b11e1..e06ce2c9 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -2314,7 +2314,8 @@
void printCurrentCpuState(StringBuilder report, long time) {
synchronized (mProcessCpuTracker) {
- report.append(mProcessCpuTracker.printCurrentState(time));
+ // Only print the first 10 processes
+ report.append(mProcessCpuTracker.printCurrentState(time, /* maxProcesses= */10));
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b505396..43c8032d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11510,6 +11510,11 @@
public void onCapturedContentResize(int width, int height) {
// Ignore resize of the captured content.
}
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ // Ignore visibility changes of the captured content.
+ }
};
UnregisterOnStopCallback mProjectionCallback;
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index 3c0fda8..c0a238f 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -307,6 +307,9 @@
static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
ProgramIdentifier hwId = new ProgramIdentifier();
hwId.type = id.getType();
+ if (hwId.type == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+ hwId.type = IdentifierType.DAB_SID_EXT;
+ }
hwId.value = id.getValue();
return hwId;
}
@@ -317,9 +320,49 @@
if (id.type == IdentifierType.INVALID) {
return null;
}
- return new ProgramSelector.Identifier(id.type, id.value);
+ int idType;
+ if (id.type == IdentifierType.DAB_SID_EXT) {
+ idType = ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
+ } else {
+ idType = id.type;
+ }
+ return new ProgramSelector.Identifier(idType, id.value);
}
+ private static boolean isVendorIdentifierType(int idType) {
+ return idType >= IdentifierType.VENDOR_START && idType <= IdentifierType.VENDOR_END;
+ }
+
+ private static boolean isValidHalProgramSelector(
+ android.hardware.broadcastradio.ProgramSelector sel) {
+ if (sel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ
+ && sel.primaryId.type != IdentifierType.RDS_PI
+ && sel.primaryId.type != IdentifierType.HD_STATION_ID_EXT
+ && sel.primaryId.type != IdentifierType.DAB_SID_EXT
+ && sel.primaryId.type != IdentifierType.DRMO_SERVICE_ID
+ && sel.primaryId.type != IdentifierType.SXM_SERVICE_ID
+ && !isVendorIdentifierType(sel.primaryId.type)) {
+ return false;
+ }
+ if (sel.primaryId.type == IdentifierType.DAB_SID_EXT) {
+ boolean hasEnsemble = false;
+ boolean hasFrequency = false;
+ for (int i = 0; i < sel.secondaryIds.length; i++) {
+ if (sel.secondaryIds[i].type == IdentifierType.DAB_ENSEMBLE) {
+ hasEnsemble = true;
+ } else if (sel.secondaryIds[i].type == IdentifierType.DAB_FREQUENCY_KHZ) {
+ hasFrequency = true;
+ }
+ if (hasEnsemble && hasFrequency) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Nullable
static android.hardware.broadcastradio.ProgramSelector programSelectorToHalProgramSelector(
ProgramSelector sel) {
android.hardware.broadcastradio.ProgramSelector hwSel =
@@ -332,6 +375,9 @@
secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i]));
}
hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new);
+ if (!isValidHalProgramSelector(hwSel)) {
+ return null;
+ }
return hwSel;
}
@@ -344,7 +390,7 @@
@Nullable
static ProgramSelector programSelectorFromHalProgramSelector(
android.hardware.broadcastradio.ProgramSelector sel) {
- if (isEmpty(sel)) {
+ if (isEmpty(sel) || !isValidHalProgramSelector(sel)) {
return null;
}
@@ -432,7 +478,34 @@
return builder.build();
}
+ private static boolean isValidLogicallyTunedTo(ProgramIdentifier id) {
+ return id.type == IdentifierType.AMFM_FREQUENCY_KHZ || id.type == IdentifierType.RDS_PI
+ || id.type == IdentifierType.HD_STATION_ID_EXT
+ || id.type == IdentifierType.DAB_SID_EXT
+ || id.type == IdentifierType.DRMO_SERVICE_ID
+ || id.type == IdentifierType.SXM_SERVICE_ID
+ || isVendorIdentifierType(id.type);
+ }
+
+ private static boolean isValidPhysicallyTunedTo(ProgramIdentifier id) {
+ return id.type == IdentifierType.AMFM_FREQUENCY_KHZ
+ || id.type == IdentifierType.DAB_FREQUENCY_KHZ
+ || id.type == IdentifierType.DRMO_FREQUENCY_KHZ
+ || id.type == IdentifierType.SXM_CHANNEL
+ || isVendorIdentifierType(id.type);
+ }
+
+ private static boolean isValidHalProgramInfo(ProgramInfo info) {
+ return isValidHalProgramSelector(info.selector)
+ && isValidLogicallyTunedTo(info.logicallyTunedTo)
+ && isValidPhysicallyTunedTo(info.physicallyTunedTo);
+ }
+
+ @Nullable
static RadioManager.ProgramInfo programInfoFromHalProgramInfo(ProgramInfo info) {
+ if (!isValidHalProgramInfo(info)) {
+ return null;
+ }
Collection<ProgramSelector.Identifier> relatedContent = new ArrayList<>();
if (info.relatedContent != null) {
for (int i = 0; i < info.relatedContent.length; i++) {
@@ -485,7 +558,14 @@
static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) {
Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length);
for (int i = 0; i < chunk.modified.length; i++) {
- modified.add(programInfoFromHalProgramInfo(chunk.modified[i]));
+ RadioManager.ProgramInfo modifiedInfo =
+ programInfoFromHalProgramInfo(chunk.modified[i]);
+ if (modifiedInfo == null) {
+ Slogf.w(TAG, "Program info %s in program list chunk is not valid",
+ chunk.modified[i]);
+ continue;
+ }
+ modified.add(modifiedInfo);
}
Set<ProgramSelector.Identifier> removed = new ArraySet<>();
if (chunk.removed != null) {
@@ -547,10 +627,22 @@
if (isAtLeastU(targetSdkVersion)) {
return chunk;
}
- Set<RadioManager.ProgramInfo> modified = chunk.getModified();
- modified.removeIf(info -> !programInfoMeetsSdkVersionRequirement(info, targetSdkVersion));
- Set<ProgramSelector.Identifier> removed = chunk.getRemoved();
- removed.removeIf(id -> isNewIdentifierInU(id));
+ Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+ Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
+ while (modifiedIterator.hasNext()) {
+ RadioManager.ProgramInfo info = modifiedIterator.next();
+ if (programInfoMeetsSdkVersionRequirement(info, targetSdkVersion)) {
+ modified.add(info);
+ }
+ }
+ Set<ProgramSelector.Identifier> removed = new ArraySet<>();
+ Iterator<ProgramSelector.Identifier> removedIterator = chunk.getRemoved().iterator();
+ while (removedIterator.hasNext()) {
+ ProgramSelector.Identifier id = removedIterator.next();
+ if (!isNewIdentifierInU(id)) {
+ removed.add(id);
+ }
+ }
return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
}
@@ -558,7 +650,7 @@
Announcement hwAnnouncement) {
return new android.hardware.radio.Announcement(
Objects.requireNonNull(programSelectorFromHalProgramSelector(
- hwAnnouncement.selector)),
+ hwAnnouncement.selector), "Program selector can not be null"),
hwAnnouncement.type,
vendorInfoFromHalVendorKeyValues(hwAnnouncement.vendorInfo)
);
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
index 095a5fa..39b1354 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
@@ -24,6 +24,7 @@
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
import java.util.ArrayList;
import java.util.Collection;
@@ -37,6 +38,7 @@
*/
final class ProgramInfoCache {
+ private static final String TAG = "BcRadioAidlSrv.cache";
/**
* Maximum number of {@link RadioManager#ProgramInfo} elements that will be put into a
* ProgramList.Chunk.mModified array. Used to try to ensure a single ProgramList.Chunk
@@ -124,6 +126,10 @@
for (int i = 0; i < chunk.modified.length; i++) {
RadioManager.ProgramInfo programInfo =
ConversionUtils.programInfoFromHalProgramInfo(chunk.modified[i]);
+ if (programInfo == null) {
+ Slogf.e(TAG, "Program info in program info %s in chunk is not valid",
+ chunk.modified[i]);
+ }
mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
}
if (chunk.removed != null) {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 6193f23..f8c19ae 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -97,10 +97,10 @@
public void onTuneFailed(int result, ProgramSelector programSelector) {
fireLater(() -> {
+ android.hardware.radio.ProgramSelector csel =
+ ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
+ int tunerResult = ConversionUtils.halResultToTunerResult(result);
synchronized (mLock) {
- android.hardware.radio.ProgramSelector csel =
- ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
- int tunerResult = ConversionUtils.halResultToTunerResult(result);
fanoutAidlCallbackLocked((cb, sdkVersion) -> {
if (csel != null && !ConversionUtils
.programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
@@ -117,10 +117,12 @@
@Override
public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
fireLater(() -> {
+ RadioManager.ProgramInfo currentProgramInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+ Objects.requireNonNull(currentProgramInfo,
+ "Program info from AIDL HAL is invalid");
synchronized (mLock) {
- mCurrentProgramInfo =
- ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
- RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
+ mCurrentProgramInfo = currentProgramInfo;
fanoutAidlCallbackLocked((cb, sdkVersion) -> {
if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
currentProgramInfo, sdkVersion)) {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index d700ed0..fe8c238 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -203,10 +203,15 @@
Slogf.w(TAG, "Cannot tune on AIDL HAL client from non-current user");
return;
}
+ android.hardware.broadcastradio.ProgramSelector hwSel =
+ ConversionUtils.programSelectorToHalProgramSelector(selector);
+ if (hwSel == null) {
+ throw new IllegalArgumentException("tune: INVALID_ARGUMENTS for program selector");
+ }
synchronized (mLock) {
checkNotClosedLocked();
try {
- mService.tune(ConversionUtils.programSelectorToHalProgramSelector(selector));
+ mService.tune(hwSel);
} catch (RuntimeException ex) {
throw ConversionUtils.throwOnError(ex, /* action= */ "tune");
}
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index e16ca0b..0b03005 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -74,6 +74,7 @@
import android.view.WindowManagerGlobal;
import com.android.framework.protobuf.nano.MessageNano;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -389,6 +390,16 @@
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
}
+ // When config_isWindowManagerCameraCompatTreatmentEnabled is true,
+ // DisplayRotationCompatPolicy in WindowManager force rotates fullscreen activities with
+ // fixed orientation to align them with the natural orientation of the device.
+ if (ctx.getResources().getBoolean(
+ R.bool.config_isWindowManagerCameraCompatTreatmentEnabled)) {
+ Slog.v(TAG, "Disable Rotate and Crop to avoid conflicts with"
+ + " WM force rotation treatment.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
// External cameras do not need crop-rotate-scale.
if (lensFacing != CameraMetadata.LENS_FACING_FRONT
&& lensFacing != CameraMetadata.LENS_FACING_BACK) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 1217e74..e557b50 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2630,6 +2630,9 @@
}
}
+ /**
+ * Uniquely identifies a Sensor, with the combination of Type and Name.
+ */
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 5dba015..f8d6c5f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -50,7 +50,6 @@
import android.provider.DeviceConfigInterface;
import android.provider.Settings;
import android.sysprop.SurfaceFlingerProperties;
-import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
@@ -70,6 +69,7 @@
import com.android.server.LocalServices;
import com.android.server.display.utils.AmbientFilter;
import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.SensorUtils;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -683,14 +683,20 @@
}
/**
- * A utility to make this class aware of the new display configs whenever the default display is
- * changed
+ * Called when the underlying display device of the default display is changed.
+ * Some data in this class relates to the physical display of the device, and so we need to
+ * reload the configurations based on this.
+ * E.g. the brightness sensors and refresh rate capabilities depend on the physical display
+ * device that is being used, so will be reloaded.
+ *
+ * @param displayDeviceConfig configurations relating to the underlying display device.
*/
public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
mSettingsObserver.setRefreshRates(displayDeviceConfig,
/* attemptLoadingFromDeviceConfig= */ true);
mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
/* attemptLoadingFromDeviceConfig= */ true);
+ mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
}
/**
@@ -1739,6 +1745,9 @@
private SensorManager mSensorManager;
private Sensor mLightSensor;
+ private Sensor mRegisteredLightSensor;
+ private String mLightSensorType;
+ private String mLightSensorName;
private final LightSensorEventListener mLightSensorListener =
new LightSensorEventListener();
// Take it as low brightness before valid sensor data comes
@@ -1899,17 +1908,8 @@
return mLowAmbientBrightnessThresholds;
}
- public void registerLightSensor(SensorManager sensorManager, Sensor lightSensor) {
- mSensorManager = sensorManager;
- mLightSensor = lightSensor;
-
- mSensorManager.registerListener(mLightSensorListener,
- mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
- }
-
public void observe(SensorManager sensorManager) {
mSensorManager = sensorManager;
- final ContentResolver cr = mContext.getContentResolver();
mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
// DeviceConfig is accessible after system ready.
@@ -2053,6 +2053,10 @@
pw.println(" mAmbientHighBrightnessThresholds: " + d);
}
+ pw.println(" mRegisteredLightSensor: " + mRegisteredLightSensor);
+ pw.println(" mLightSensor: " + mLightSensor);
+ pw.println(" mLightSensorName: " + mLightSensorName);
+ pw.println(" mLightSensorType: " + mLightSensorType);
mLightSensorListener.dumpLocked(pw);
if (mAmbientFilter != null) {
@@ -2106,27 +2110,9 @@
}
if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
- Resources resources = mContext.getResources();
- String lightSensorType = resources.getString(
- com.android.internal.R.string.config_displayLightSensorType);
+ Sensor lightSensor = getLightSensor();
- Sensor lightSensor = null;
- if (!TextUtils.isEmpty(lightSensorType)) {
- List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
- for (int i = 0; i < sensors.size(); i++) {
- Sensor sensor = sensors.get(i);
- if (lightSensorType.equals(sensor.getStringType())) {
- lightSensor = sensor;
- break;
- }
- }
- }
-
- if (lightSensor == null) {
- lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
- }
-
- if (lightSensor != null) {
+ if (lightSensor != null && lightSensor != mLightSensor) {
final Resources res = mContext.getResources();
mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
@@ -2137,14 +2123,40 @@
mLightSensor = null;
}
+ updateSensorStatus();
if (mRefreshRateChangeable) {
- updateSensorStatus();
synchronized (mLock) {
onBrightnessChangedLocked();
}
}
}
+ private void reloadLightSensor(DisplayDeviceConfig displayDeviceConfig) {
+ reloadLightSensorData(displayDeviceConfig);
+ restartObserver();
+ }
+
+ private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
+ // The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
+ // it naturally falls back to the global config.xml.
+ if (displayDeviceConfig != null
+ && displayDeviceConfig.getAmbientLightSensor() != null) {
+ // This covers both the ddc and the config.xml fallback
+ mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
+ mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
+ } else if (mLightSensorName == null && mLightSensorType == null) {
+ Resources resources = mContext.getResources();
+ mLightSensorType = resources.getString(
+ com.android.internal.R.string.config_displayLightSensorType);
+ mLightSensorName = "";
+ }
+ }
+
+ private Sensor getLightSensor() {
+ return SensorUtils.findSensor(mSensorManager, mLightSensorType,
+ mLightSensorName, Sensor.TYPE_LIGHT);
+ }
+
/**
* Checks to see if at least one value is positive, in which case it is necessary to listen
* to value changes.
@@ -2288,17 +2300,36 @@
if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
&& isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
- mSensorManager.registerListener(mLightSensorListener,
- mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
- if (mLoggingEnabled) {
- Slog.d(TAG, "updateSensorStatus: registerListener");
- }
+ registerLightSensor();
+
} else {
- mLightSensorListener.removeCallbacks();
- mSensorManager.unregisterListener(mLightSensorListener);
- if (mLoggingEnabled) {
- Slog.d(TAG, "updateSensorStatus: unregisterListener");
- }
+ unregisterSensorListener();
+ }
+ }
+
+ private void registerLightSensor() {
+ if (mRegisteredLightSensor == mLightSensor) {
+ return;
+ }
+
+ if (mRegisteredLightSensor != null) {
+ unregisterSensorListener();
+ }
+
+ mSensorManager.registerListener(mLightSensorListener,
+ mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+ mRegisteredLightSensor = mLightSensor;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateSensorStatus: registerListener");
+ }
+ }
+
+ private void unregisterSensorListener() {
+ mLightSensorListener.removeCallbacks();
+ mSensorManager.unregisterListener(mLightSensorListener);
+ mRegisteredLightSensor = null;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateSensorStatus: unregisterListener");
}
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 5a5be4e..ddeaa1b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -621,6 +621,13 @@
// expect), and there will still be letterboxing on the output content since the
// Surface and VirtualDisplay would then have different aspect ratios.
}
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ // Do nothing when we tell the client that the content has a visibility change - it is
+ // up to them to decide to pause recording, and update their own UI, depending on their
+ // use case.
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 4924ad5..48bc46c 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -33,6 +33,10 @@
*/
public static Sensor findSensor(SensorManager sensorManager, String sensorType,
String sensorName, int fallbackType) {
+ if (sensorManager == null) {
+ return null;
+ }
+
if ("".equals(sensorName) && "".equals(sensorType)) {
return null;
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
new file mode 100644
index 0000000..11a4294
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.IGrammaticalInflectionManager;
+import android.content.Context;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * The implementation of IGrammaticalInflectionManager.aidl.
+ *
+ * <p>This service is API entry point for storing app-specific grammatical inflection.
+ */
+public class GrammaticalInflectionService extends SystemService {
+
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ /**
+ * Initializes the system service.
+ * <p>
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ * </p>
+ *
+ * @param context The system server context.
+ *
+ * @hide
+ */
+ public GrammaticalInflectionService(Context context) {
+ super(context);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+ }
+
+ private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+ @Override
+ public void setRequestedApplicationGrammaticalGender(
+ String appPackageName, int userId, int gender) {
+ GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
+ appPackageName, userId, gender);
+ }
+ };
+
+ private void setRequestedApplicationGrammaticalGender(
+ String appPackageName, int userId, int gender) {
+ final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+ mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
+ userId);
+
+ updater.setGrammaticalGender(gender).commit();
+ }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index d76da83..b8eb901 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -385,8 +385,10 @@
R.styleable.KeyboardLayout_keyboardLayout,
0);
String languageTags = a.getString(
- R.styleable.KeyboardLayout_locale);
+ R.styleable.KeyboardLayout_keyboardLocale);
LocaleList locales = getLocalesFromLanguageTags(languageTags);
+ int layoutType = a.getInt(R.styleable.KeyboardLayout_keyboardLayoutType,
+ 0);
int vid = a.getInt(
R.styleable.KeyboardLayout_vendorId, -1);
int pid = a.getInt(
@@ -403,7 +405,7 @@
if (keyboardName == null || name.equals(keyboardName)) {
KeyboardLayout layout = new KeyboardLayout(
descriptor, label, collection, priority,
- locales, vid, pid);
+ locales, layoutType, vid, pid);
visitor.visitKeyboardLayout(
resources, keyboardLayoutResId, layout);
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
new file mode 100644
index 0000000..60167b4
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.Manifest;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.inputmethod.ImeTracker;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.view.IImeTracker;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayDeque;
+import java.util.Locale;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Service for managing and logging {@link ImeTracker.Token} instances.
+ *
+ * @implNote Suppresses {@link GuardedBy} warnings, as linter reports that {@link #mHistory}
+ * interactions are guarded by {@code this} instead of {@code ImeTrackerService.this}, which should
+ * be identical.
+ *
+ * @hide
+ */
+@SuppressWarnings("GuardedBy")
+public final class ImeTrackerService extends IImeTracker.Stub {
+
+ static final String TAG = "ImeTrackerService";
+
+ /** The threshold in milliseconds after which a history entry is considered timed out. */
+ private static final long TIMEOUT_MS = 10_000;
+
+ /** Handler for registering timeouts for live entries. */
+ private final Handler mHandler =
+ new Handler(Looper.myLooper(), null /* callback */, true /* async */);
+
+ /** Singleton instance of the History. */
+ @GuardedBy("ImeTrackerService.this")
+ private final History mHistory = new History();
+
+ @NonNull
+ @Override
+ public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
+ @SoftInputShowHideReason int reason) {
+ final IBinder binder = new Binder();
+ final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
+ ImeTracker.STATUS_RUN, origin, reason);
+ mHistory.addEntry(binder, entry);
+
+ // Register a delayed task to handle the case where the new entry times out.
+ mHandler.postDelayed(() -> {
+ synchronized (ImeTrackerService.this) {
+ mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+ }
+ }, TIMEOUT_MS);
+
+ return binder;
+ }
+
+ @NonNull
+ @Override
+ public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
+ @SoftInputShowHideReason int reason) {
+ final IBinder binder = new Binder();
+ final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
+ ImeTracker.STATUS_RUN, origin, reason);
+ mHistory.addEntry(binder, entry);
+
+ // Register a delayed task to handle the case where the new entry times out.
+ mHandler.postDelayed(() -> {
+ synchronized (ImeTrackerService.this) {
+ mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+ }
+ }, TIMEOUT_MS);
+
+ return binder;
+ }
+
+ @Override
+ public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ final History.Entry entry = mHistory.getEntry(statsToken);
+ if (entry == null) return;
+
+ entry.mPhase = phase;
+ }
+
+ @Override
+ public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+ }
+
+ @Override
+ public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+ }
+
+ @Override
+ public synchronized void onShown(@NonNull IBinder statsToken) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
+
+ @Override
+ public synchronized void onHidden(@NonNull IBinder statsToken) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
+
+ /**
+ * Updates the IME request tracking token with new information available in IMMS.
+ *
+ * @param statsToken the token corresponding to the current IME request.
+ * @param requestWindowName the name of the window that created the IME request.
+ */
+ public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+ @NonNull String requestWindowName) {
+ final History.Entry entry = mHistory.getEntry(statsToken);
+ if (entry == null) return;
+
+ entry.mRequestWindowName = requestWindowName;
+ }
+
+ /** Dumps the contents of the history. */
+ public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ mHistory.dump(pw, prefix);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public synchronized boolean hasPendingImeVisibilityRequests() {
+ super.hasPendingImeVisibilityRequests_enforcePermission();
+
+ return !mHistory.mLiveEntries.isEmpty();
+ }
+
+ /**
+ * A circular buffer storing the most recent few {@link ImeTracker.Token} entries information.
+ */
+ private static final class History {
+
+ /** The circular buffer's capacity. */
+ private static final int CAPACITY = 100;
+
+ /** Backing store for the circular buffer. */
+ @GuardedBy("ImeTrackerService.this")
+ private final ArrayDeque<Entry> mEntries = new ArrayDeque<>(CAPACITY);
+
+ /** Backing store for the live entries (i.e. entries that are not finished yet). */
+ @GuardedBy("ImeTrackerService.this")
+ private final WeakHashMap<IBinder, Entry> mLiveEntries = new WeakHashMap<>();
+
+ /** Latest entry sequence number. */
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ /** Adds a live entry. */
+ @GuardedBy("ImeTrackerService.this")
+ private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
+ mLiveEntries.put(statsToken, entry);
+ }
+
+ /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+ @Nullable
+ @GuardedBy("ImeTrackerService.this")
+ private Entry getEntry(@NonNull IBinder statsToken) {
+ return mLiveEntries.get(statsToken);
+ }
+
+ /**
+ * Sets the live entry corresponding to the tracking token, if it exists, as finished,
+ * and uploads the data for metrics.
+ *
+ * @param statsToken the token corresponding to the current IME request.
+ * @param status the finish status of the IME request.
+ * @param phase the phase the IME request finished at, if it exists
+ * (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
+ */
+ @GuardedBy("ImeTrackerService.this")
+ private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
+ @ImeTracker.Phase int phase) {
+ final Entry entry = mLiveEntries.remove(statsToken);
+ if (entry == null) return;
+
+ entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
+ entry.mStatus = status;
+
+ if (phase != ImeTracker.PHASE_NOT_SET) {
+ entry.mPhase = phase;
+ }
+
+ // Remove excess entries overflowing capacity (plus one for the new entry).
+ while (mEntries.size() >= CAPACITY) {
+ mEntries.remove();
+ }
+
+ mEntries.offer(entry);
+
+ // Log newly finished entry.
+ FrameworkStatsLog.write(FrameworkStatsLog.IME_REQUEST_FINISHED, entry.mUid,
+ entry.mDuration, entry.mType, entry.mStatus, entry.mReason,
+ entry.mOrigin, entry.mPhase);
+ }
+
+ /** Dumps the contents of the circular buffer. */
+ @GuardedBy("ImeTrackerService.this")
+ private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ pw.print(prefix);
+ pw.println("ImeTrackerService#History.mLiveEntries:");
+
+ for (final Entry entry: mLiveEntries.values()) {
+ dumpEntry(entry, pw, prefix, formatter);
+ }
+
+ pw.print(prefix);
+ pw.println("ImeTrackerService#History.mEntries:");
+
+ for (final Entry entry: mEntries) {
+ dumpEntry(entry, pw, prefix, formatter);
+ }
+ }
+
+ @GuardedBy("ImeTrackerService.this")
+ private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
+ @NonNull String prefix, @NonNull DateTimeFormatter formatter) {
+ pw.print(prefix);
+ pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+
+ pw.print(prefix);
+ pw.println(" duration=" + entry.mDuration + "ms");
+
+ pw.print(prefix);
+ pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
+
+ pw.print(prefix);
+ pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
+
+ pw.print(prefix);
+ pw.print(" origin="
+ + ImeTracker.Debug.originToString(entry.mOrigin));
+
+ pw.print(prefix);
+ pw.print(" reason="
+ + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+
+ pw.print(prefix);
+ pw.print(" phase="
+ + ImeTracker.Debug.phaseToString(entry.mPhase));
+
+ pw.print(prefix);
+ pw.print(" requestWindowName=" + entry.mRequestWindowName);
+ }
+
+ /** A history entry. */
+ private static final class Entry {
+
+ /** The entry's sequence number in the history. */
+ private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+
+ /** Uid of the client that requested the IME. */
+ private final int mUid;
+
+ /** Clock time in milliseconds when the IME request was created. */
+ private final long mStartTime = System.currentTimeMillis();
+
+ /** Duration in milliseconds of the IME request from start to end. */
+ private long mDuration = 0;
+
+ /** Type of the IME request. */
+ @ImeTracker.Type
+ private final int mType;
+
+ /** Status of the IME request. */
+ @ImeTracker.Status
+ private int mStatus;
+
+ /** Origin of the IME request. */
+ @ImeTracker.Origin
+ private final int mOrigin;
+
+ /** Reason for creating the IME request. */
+ @SoftInputShowHideReason
+ private final int mReason;
+
+ /** Latest phase of the IME request. */
+ @ImeTracker.Phase
+ private int mPhase = ImeTracker.PHASE_NOT_SET;
+
+ /**
+ * Name of the window that created the IME request.
+ *
+ * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+ */
+ @NonNull
+ private String mRequestWindowName = "not set";
+
+ private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ mUid = uid;
+ mType = type;
+ mStatus = status;
+ mOrigin = origin;
+ mReason = reason;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5840acf..c15b538 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -176,6 +176,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.view.IImeTracker;
import com.android.internal.view.IInputMethodManager;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
@@ -198,11 +199,12 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.security.InvalidParameterException;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -919,8 +921,9 @@
}
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final SimpleDateFormat dataFormat =
- new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
for (int i = 0; i < mEntries.length; ++i) {
final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -931,7 +934,7 @@
pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":");
pw.print(prefix);
- pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ " (timestamp=" + entry.mTimestamp + ")");
pw.print(prefix);
@@ -999,7 +1002,7 @@
private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
/**
- * Entry size for non low-RAM devices.
+ * Entry size for low-RAM devices.
*
* <p>TODO: Consider to follow what other system services have been doing to manage
* constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
@@ -1015,7 +1018,7 @@
}
/**
- * Backing store for the ring bugger.
+ * Backing store for the ring buffer.
*/
private final Entry[] mEntries = new Entry[getEntrySize()];
@@ -1095,8 +1098,9 @@
}
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final SimpleDateFormat dataFormat =
- new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
for (int i = 0; i < mEntries.length; ++i) {
final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -1107,7 +1111,7 @@
pw.println("StartInput #" + entry.mSequenceNumber + ":");
pw.print(prefix);
- pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ " (timestamp=" + entry.mTimestamp + ")"
+ " reason="
+ InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
@@ -1149,6 +1153,9 @@
private final SoftInputShowHideHistory mSoftInputShowHideHistory =
new SoftInputShowHideHistory();
+ @NonNull
+ private final ImeTrackerService mImeTrackerService = new ImeTrackerService();
+
class SettingsObserver extends ContentObserver {
int mUserId;
boolean mRegistered = false;
@@ -3405,13 +3412,11 @@
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
// Create statsToken is none exists.
if (statsToken == null) {
- String packageName = null;
- if (mCurEditorInfo != null) {
- packageName = mCurEditorInfo.packageName;
- }
- statsToken = new ImeTracker.Token(packageName);
- ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT,
- reason);
+ // TODO(b/261565259): to avoid using null, add package name in ClientState
+ final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
+ final int uid = mCurClient != null ? mCurClient.mUid : -1;
+ statsToken = ImeTracker.get().onRequestShow(packageName, uid,
+ ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
}
mShowRequested = true;
@@ -3461,7 +3466,7 @@
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
}
- onShowHideSoftInputRequested(true /* show */, windowToken, reason);
+ onShowHideSoftInputRequested(true /* show */, windowToken, reason, statsToken);
}
mInputShown = true;
return true;
@@ -3508,12 +3513,18 @@
int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
// Create statsToken is none exists.
if (statsToken == null) {
- String packageName = null;
- if (mCurEditorInfo != null) {
- packageName = mCurEditorInfo.packageName;
+ // TODO(b/261565259): to avoid using null, add package name in ClientState
+ final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
+ final int uid;
+ if (mCurClient != null) {
+ uid = mCurClient.mUid;
+ } else if (mCurFocusedWindowClient != null) {
+ uid = mCurFocusedWindowClient.mUid;
+ } else {
+ uid = -1;
}
- statsToken = new ImeTracker.Token(packageName);
- ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
+ statsToken = ImeTracker.get().onRequestHide(packageName, uid,
+ ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
}
if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
@@ -3565,7 +3576,7 @@
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
}
- onShowHideSoftInputRequested(false /* show */, windowToken, reason);
+ onShowHideSoftInputRequested(false /* show */, windowToken, reason, statsToken);
}
res = true;
} else {
@@ -4781,6 +4792,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
return;
}
if (!setVisible) {
@@ -4846,7 +4858,7 @@
/** Called right after {@link com.android.internal.inputmethod.IInputMethod#showSoftInput}. */
@GuardedBy("ImfLock.class")
private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
- @SoftInputShowHideReason int reason) {
+ @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
@@ -4855,6 +4867,8 @@
mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
info.imeSurfaceParentName));
+
+ mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
}
@BinderThread
@@ -5994,6 +6008,9 @@
p.println(" mSoftInputShowHideHistory:");
mSoftInputShowHideHistory.dump(pw, " ");
+
+ p.println(" mImeTrackerService#History:");
+ mImeTrackerService.dump(pw, " ");
}
// Exit here for critical dump, as remaining sections require IPCs to other processes.
@@ -6584,6 +6601,12 @@
return true;
}
+ /** @hide */
+ @Override
+ public IImeTracker getImeTrackerService() {
+ return mImeTrackerService;
+ }
+
private static final class InputMethodPrivilegedOperationsImpl
extends IInputMethodPrivilegedOperations.Stub {
private final InputMethodManagerService mImms;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 89bc495a..121b7c8 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -173,7 +173,6 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
-import java.util.Random;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.CountDownLatch;
@@ -234,7 +233,6 @@
private final SynchronizedStrongAuthTracker mStrongAuthTracker;
private final BiometricDeferredQueue mBiometricDeferredQueue;
private final LongSparseArray<byte[]> mGatekeeperPasswords;
- private final Random mRandom;
private final NotificationManager mNotificationManager;
protected final UserManager mUserManager;
@@ -348,23 +346,17 @@
}
private LockscreenCredential generateRandomProfilePassword() {
- byte[] randomLockSeed = new byte[] {};
- try {
- randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
- char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
- byte[] newPassword = new byte[newPasswordChars.length];
- for (int i = 0; i < newPasswordChars.length; i++) {
- newPassword[i] = (byte) newPasswordChars[i];
- }
- LockscreenCredential credential =
- LockscreenCredential.createManagedPassword(newPassword);
- Arrays.fill(newPasswordChars, '\u0000');
- Arrays.fill(newPassword, (byte) 0);
- Arrays.fill(randomLockSeed, (byte) 0);
- return credential;
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Fail to generate profile password", e);
+ byte[] randomLockSeed = SecureRandomUtils.randomBytes(40);
+ char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
+ byte[] newPassword = new byte[newPasswordChars.length];
+ for (int i = 0; i < newPasswordChars.length; i++) {
+ newPassword[i] = (byte) newPasswordChars[i];
}
+ LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword);
+ Arrays.fill(newPasswordChars, '\u0000');
+ Arrays.fill(newPassword, (byte) 0);
+ Arrays.fill(randomLockSeed, (byte) 0);
+ return credential;
}
/**
@@ -597,7 +589,6 @@
mStrongAuthTracker = injector.getStrongAuthTracker();
mStrongAuthTracker.register(mStrongAuth);
mGatekeeperPasswords = new LongSparseArray<>();
- mRandom = new SecureRandom();
mSpManager = injector.getSyntheticPasswordManager(mStorage);
mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
@@ -1752,14 +1743,9 @@
private String getSalt(int userId) {
long salt = getLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 0, userId);
if (salt == 0) {
- try {
- salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
- setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
- Slog.v(TAG, "Initialized lock password salt for user: " + userId);
- } catch (NoSuchAlgorithmException e) {
- // Throw an exception rather than storing a password we'll never be able to recover
- throw new IllegalStateException("Couldn't get SecureRandom number", e);
- }
+ salt = SecureRandomUtils.randomLong();
+ setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
+ Slog.v(TAG, "Initialized lock password salt for user: " + userId);
}
return Long.toHexString(salt);
}
@@ -2644,7 +2630,7 @@
synchronized (mGatekeeperPasswords) {
while (handle == 0L || mGatekeeperPasswords.get(handle) != null) {
- handle = mRandom.nextLong();
+ handle = SecureRandomUtils.randomLong();
}
mGatekeeperPasswords.put(handle, gatekeeperPassword);
}
diff --git a/services/core/java/com/android/server/locksettings/SecureRandomUtils.java b/services/core/java/com/android/server/locksettings/SecureRandomUtils.java
new file mode 100644
index 0000000..4ba4dd0
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/SecureRandomUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import java.security.SecureRandom;
+
+/** Utilities using a static SecureRandom */
+public class SecureRandomUtils {
+ private static final SecureRandom RNG = new SecureRandom();
+
+ /** Use SecureRandom to generate `length` random bytes */
+ public static byte[] randomBytes(int length) {
+ byte[] res = new byte[length];
+ RNG.nextBytes(res);
+ return res;
+ }
+
+ /** Use SecureRandom to generate a random long */
+ public static long randomLong() {
+ return RNG.nextLong();
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index ad2fa22..cd972dc 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -59,8 +59,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -284,8 +282,10 @@
*/
static SyntheticPassword create() {
SyntheticPassword result = new SyntheticPassword(SYNTHETIC_PASSWORD_VERSION_V3);
- byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
- byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+ byte[] escrowSplit0 =
+ SecureRandomUtils.randomBytes(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+ byte[] escrowSplit1 =
+ SecureRandomUtils.randomBytes(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
result.recreate(escrowSplit0, escrowSplit1);
byte[] encrypteEscrowSplit0 = SyntheticPasswordCrypto.encrypt(result.mSyntheticPassword,
PERSONALIZATION_E0, escrowSplit0);
@@ -347,7 +347,7 @@
result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
result.credentialType = credentialType;
- result.salt = secureRandom(PASSWORD_SALT_LENGTH);
+ result.salt = SecureRandomUtils.randomBytes(PASSWORD_SALT_LENGTH);
return result;
}
@@ -490,7 +490,7 @@
android.hardware.weaver.V1_0.IWeaver hidlWeaver = getWeaverHidlService();
if (hidlWeaver != null) {
Slog.i(TAG, "Using HIDL weaver service");
- return new WeaverHidlWrapper(hidlWeaver);
+ return new WeaverHidlAdapter(hidlWeaver);
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get HIDL weaver service.", e);
@@ -552,7 +552,7 @@
throw new IllegalArgumentException("Invalid key size for weaver");
}
if (value == null) {
- value = secureRandom(mWeaverConfig.valueSize);
+ value = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
}
try {
mWeaver.write(slot, key, value);
@@ -1039,9 +1039,9 @@
}
TokenData tokenData = new TokenData();
tokenData.mType = type;
- final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
+ final byte[] secdiscardable = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH);
if (isWeaverAvailable()) {
- tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
+ tokenData.weaverSecret = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
} else {
@@ -1510,7 +1510,7 @@
* been created.
*/
private byte[] createSecdiscardable(long protectorId, int userId) {
- byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
+ byte[] data = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH);
saveSecdiscardable(protectorId, data, userId);
return data;
}
@@ -1624,12 +1624,12 @@
}
private static long generateProtectorId() {
- SecureRandom rng = new SecureRandom();
- long result;
- do {
- result = rng.nextLong();
- } while (result == NULL_PROTECTOR_ID);
- return result;
+ while (true) {
+ final long result = SecureRandomUtils.randomLong();
+ if (result != NULL_PROTECTOR_ID) {
+ return result;
+ }
+ }
}
@VisibleForTesting
@@ -1637,15 +1637,6 @@
return 100000 + userId;
}
- protected static byte[] secureRandom(int length) {
- try {
- return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- return null;
- }
- }
-
private String getProtectorKeyAlias(long protectorId) {
return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
}
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java b/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java
new file mode 100644
index 0000000..2e9c3fd
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.hardware.weaver.IWeaver;
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+import android.hardware.weaver.WeaverReadStatus;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Adapt the legacy HIDL interface to present the AIDL interface.
+ */
+class WeaverHidlAdapter implements IWeaver {
+ private static final String TAG = "WeaverHidlAdapter";
+ private final android.hardware.weaver.V1_0.IWeaver mImpl;
+
+ WeaverHidlAdapter(android.hardware.weaver.V1_0.IWeaver impl) {
+ mImpl = impl;
+ }
+
+ @Override
+ public WeaverConfig getConfig() throws RemoteException {
+ final WeaverConfig[] res = new WeaverConfig[1];
+ mImpl.getConfig((int status, android.hardware.weaver.V1_0.WeaverConfig config) -> {
+ if (status == android.hardware.weaver.V1_0.WeaverStatus.OK) {
+ WeaverConfig aidlRes = new WeaverConfig();
+ aidlRes.slots = config.slots;
+ aidlRes.keySize = config.keySize;
+ aidlRes.valueSize = config.valueSize;
+ res[0] = aidlRes;
+ } else {
+ Slog.e(TAG,
+ "Failed to get HIDL weaver config. status: " + status
+ + ", slots: " + config.slots);
+ }
+ });
+ return res[0];
+ }
+
+ @Override
+ public WeaverReadResponse read(int slotId, byte[] key)
+ throws RemoteException {
+ final WeaverReadResponse[] res = new WeaverReadResponse[1];
+ mImpl.read(
+ slotId, toByteArrayList(key),
+ (int inStatus, android.hardware.weaver.V1_0.WeaverReadResponse readResponse) -> {
+ WeaverReadResponse aidlRes =
+ new WeaverReadResponse();
+ switch (inStatus) {
+ case android.hardware.weaver.V1_0.WeaverReadStatus.OK:
+ aidlRes.status = WeaverReadStatus.OK;
+ break;
+ case android.hardware.weaver.V1_0.WeaverReadStatus.THROTTLE:
+ aidlRes.status = WeaverReadStatus.THROTTLE;
+ break;
+ case android.hardware.weaver.V1_0.WeaverReadStatus.INCORRECT_KEY:
+ aidlRes.status = WeaverReadStatus.INCORRECT_KEY;
+ break;
+ case android.hardware.weaver.V1_0.WeaverReadStatus.FAILED:
+ aidlRes.status = WeaverReadStatus.FAILED;
+ break;
+ default:
+ Slog.e(TAG, "Unexpected status in read: " + inStatus);
+ aidlRes.status = WeaverReadStatus.FAILED;
+ break;
+ }
+ aidlRes.timeout = readResponse.timeout;
+ aidlRes.value = fromByteArrayList(readResponse.value);
+ res[0] = aidlRes;
+ });
+ return res[0];
+ }
+
+ @Override
+ public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
+ int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
+ if (writeStatus != android.hardware.weaver.V1_0.WeaverStatus.OK) {
+ throw new ServiceSpecificException(
+ IWeaver.STATUS_FAILED, "Failed IWeaver.write call, status: " + writeStatus);
+ }
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ // We do not require the interface hash as the client.
+ throw new UnsupportedOperationException(
+ "WeaverHidlAdapter does not support getInterfaceHash");
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ // Supports only V2 which is at feature parity.
+ return 2;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // There is no IHwBinder to IBinder. Not required as the client.
+ throw new UnsupportedOperationException("WeaverHidlAdapter does not support asBinder");
+ }
+
+ private static ArrayList<Byte> toByteArrayList(byte[] data) {
+ ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+ for (int i = 0; i < data.length; i++) {
+ result.add(data[i]);
+ }
+ return result;
+ }
+
+ private static byte[] fromByteArrayList(ArrayList<Byte> data) {
+ byte[] result = new byte[data.size()];
+ for (int i = 0; i < data.size(); i++) {
+ result[i] = data.get(i);
+ }
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java b/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
deleted file mode 100644
index 9d93c3d..0000000
--- a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings;
-
-import android.hardware.weaver.V1_0.IWeaver;
-import android.hardware.weaver.V1_0.WeaverConfig;
-import android.hardware.weaver.V1_0.WeaverReadResponse;
-import android.hardware.weaver.V1_0.WeaverReadStatus;
-import android.hardware.weaver.V1_0.WeaverStatus;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Slog;
-
-import java.util.ArrayList;
-
-/**
- * Implement the AIDL IWeaver interface wrapping the HIDL implementation
- */
-class WeaverHidlWrapper implements android.hardware.weaver.IWeaver {
- private static final String TAG = "WeaverHidlWrapper";
- private final IWeaver mImpl;
-
- WeaverHidlWrapper(IWeaver impl) {
- mImpl = impl;
- }
-
- private static ArrayList<Byte> toByteArrayList(byte[] data) {
- ArrayList<Byte> result = new ArrayList<Byte>(data.length);
- for (int i = 0; i < data.length; i++) {
- result.add(data[i]);
- }
- return result;
- }
-
- private static byte[] fromByteArrayList(ArrayList<Byte> data) {
- byte[] result = new byte[data.size()];
- for (int i = 0; i < data.size(); i++) {
- result[i] = data.get(i);
- }
- return result;
- }
-
- @Override
- public String getInterfaceHash() {
- // We do not require the interface hash as the client.
- throw new UnsupportedOperationException(
- "WeaverHidlWrapper does not support getInterfaceHash");
- }
- @Override
- public int getInterfaceVersion() {
- // Supports only V2 which is at feature parity.
- return 2;
- }
- @Override
- public android.os.IBinder asBinder() {
- // There is no IHwBinder to IBinder. Not required as the client.
- throw new UnsupportedOperationException("WeaverHidlWrapper does not support asBinder");
- }
-
- @Override
- public android.hardware.weaver.WeaverConfig getConfig() throws RemoteException {
- final WeaverConfig[] res = new WeaverConfig[1];
- mImpl.getConfig((int status, WeaverConfig config) -> {
- if (status == WeaverStatus.OK && config.slots > 0) {
- res[0] = config;
- } else {
- res[0] = null;
- Slog.e(TAG,
- "Failed to get HIDL weaver config. status: " + status
- + ", slots: " + config.slots);
- }
- });
-
- if (res[0] == null) {
- return null;
- }
- android.hardware.weaver.WeaverConfig config = new android.hardware.weaver.WeaverConfig();
- config.slots = res[0].slots;
- config.keySize = res[0].keySize;
- config.valueSize = res[0].valueSize;
- return config;
- }
-
- @Override
- public android.hardware.weaver.WeaverReadResponse read(int slotId, byte[] key)
- throws RemoteException {
- final WeaverReadResponse[] res = new WeaverReadResponse[1];
- final int[] status = new int[1];
- mImpl.read(
- slotId, toByteArrayList(key), (int inStatus, WeaverReadResponse readResponse) -> {
- status[0] = inStatus;
- res[0] = readResponse;
- });
-
- android.hardware.weaver.WeaverReadResponse aidlRes =
- new android.hardware.weaver.WeaverReadResponse();
- switch (status[0]) {
- case WeaverReadStatus.OK:
- aidlRes.status = android.hardware.weaver.WeaverReadStatus.OK;
- break;
- case WeaverReadStatus.THROTTLE:
- aidlRes.status = android.hardware.weaver.WeaverReadStatus.THROTTLE;
- break;
- case WeaverReadStatus.INCORRECT_KEY:
- aidlRes.status = android.hardware.weaver.WeaverReadStatus.INCORRECT_KEY;
- break;
- case WeaverReadStatus.FAILED:
- aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
- break;
- default:
- aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
- break;
- }
- if (res[0] != null) {
- aidlRes.timeout = res[0].timeout;
- aidlRes.value = fromByteArrayList(res[0].value);
- }
- return aidlRes;
- }
-
- @Override
- public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
- int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
- if (writeStatus != WeaverStatus.OK) {
- throw new ServiceSpecificException(
- android.hardware.weaver.IWeaver.STATUS_FAILED, "Failed IWeaver.write call");
- }
- }
-}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 50e1fca..e9ee750 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -370,6 +370,26 @@
}
}
+ @Override
+ public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+ + "on captured content resize");
+ }
+ if (!isValidMediaProjection(mProjectionGrant)) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mProjectionGrant != null && mCallbackDelegate != null) {
+ mCallbackDelegate.dispatchVisibilityChanged(mProjectionGrant, isVisible);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override //Binder call
public void addCallback(final IMediaProjectionWatcherCallback callback) {
if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
@@ -750,8 +770,9 @@
public void dispatchResize(MediaProjection projection, int width, int height) {
if (projection == null) {
- Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
- + " Ignoring!");
+ Slog.e(TAG,
+ "Tried to dispatch resize notification for a null media projection. "
+ + "Ignoring!");
return;
}
synchronized (mLock) {
@@ -774,6 +795,36 @@
// is for passing along if recording is still ongoing or not.
}
}
+
+ public void dispatchVisibilityChanged(MediaProjection projection, boolean isVisible) {
+ if (projection == null) {
+ Slog.e(TAG,
+ "Tried to dispatch visibility changed notification for a null media "
+ + "projection. Ignoring!");
+ return;
+ }
+ synchronized (mLock) {
+ // TODO(b/249827847) Currently the service assumes there is only one projection
+ // at once - need to find the callback for the given projection, when there are
+ // multiple sessions.
+ for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+ mHandler.post(() -> {
+ try {
+ // Notify every callback the client has registered for a particular
+ // MediaProjection instance.
+ callback.onCapturedContentVisibilityChanged(isVisible);
+ } catch (RemoteException e) {
+ Slog.w(TAG,
+ "Failed to notify media projection has captured content "
+ + "visibility change to "
+ + isVisible, e);
+ }
+ });
+ }
+ // Do not need to notify watcher callback about visibility changes, since watcher
+ // callback is for passing along if recording is still ongoing or not.
+ }
+ }
}
private static final class WatcherStartCallback implements Runnable {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index d873736..0a59c19 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2045,7 +2045,8 @@
}
final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
- ps.getPkg().getStaticSharedLibraryName(), ps.getPkg().getStaticSharedLibVersion());
+ ps.getPkg().getStaticSharedLibraryName(),
+ ps.getPkg().getStaticSharedLibraryVersion());
if (libraryInfo == null) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 9ea1807..3df46a2 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -186,7 +186,7 @@
SharedLibraryInfo libraryInfo = null;
if (pkg.getStaticSharedLibraryName() != null) {
libraryInfo = computer.getSharedLibraryInfo(pkg.getStaticSharedLibraryName(),
- pkg.getStaticSharedLibVersion());
+ pkg.getStaticSharedLibraryVersion());
} else if (pkg.getSdkLibraryName() != null) {
libraryInfo = computer.getSharedLibraryInfo(pkg.getSdkLibraryName(),
pkg.getSdkLibVersionMajor());
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7049f9a..16bf0fe 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -297,7 +297,7 @@
SharedUserSetting sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(pkgSetting);
if (sharedUserSetting != null) {
sharedUserSetting.addPackage(pkgSetting);
- if (parsedPackage.isLeavingSharedUid()
+ if (parsedPackage.isLeavingSharedUser()
&& SharedUidMigration.applyStrategy(BEST_EFFORT)
&& sharedUserSetting.isSingleUser()) {
// Attempt the transparent shared UID migration
@@ -1552,7 +1552,7 @@
}
// APK should not re-join shared UID
- if (oldPackage.isLeavingSharedUid() && !parsedPackage.isLeavingSharedUid()) {
+ if (oldPackage.isLeavingSharedUser() && !parsedPackage.isLeavingSharedUser()) {
throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
"Package " + parsedPackage.getPackageName()
+ " attempting to rejoin " + newSharedUid);
@@ -3801,7 +3801,7 @@
if (installedPkgSetting == null || !installedPkgSetting.hasSharedUser()) {
// Directly ignore sharedUserSetting for new installs, or if the app has
// already left shared UID
- ignoreSharedUserId = parsedPackage.isLeavingSharedUid();
+ ignoreSharedUserId = parsedPackage.isLeavingSharedUser();
}
if (!ignoreSharedUserId && parsedPackage.getSharedUserId() != null) {
@@ -4324,10 +4324,10 @@
SharedLibraryInfo libInfo = versionedLib.valueAt(i);
final long libVersionCode = libInfo.getDeclaringPackage()
.getLongVersionCode();
- if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) {
+ if (libInfo.getLongVersion() < pkg.getStaticSharedLibraryVersion()) {
minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
} else if (libInfo.getLongVersion()
- > pkg.getStaticSharedLibVersion()) {
+ > pkg.getStaticSharedLibraryVersion()) {
maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
} else {
minVersionCode = maxVersionCode = libVersionCode;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 9c60795..8c5bab6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1211,7 +1211,8 @@
// Take a short detour to confirm with user
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
- intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
+ intent.putExtra(PackageInstaller.EXTRA_CALLBACK,
+ new PackageManager.UninstallCompleteCallback(adapter.getBinder().asBinder()));
adapter.onUserActionRequired(intent);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ed4c849..afcd9d1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2579,6 +2579,10 @@
: PackageInstaller.ACTION_CONFIRM_INSTALL);
intent.setPackage(mPm.getPackageInstallerPackageName());
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ synchronized (mLock) {
+ intent.putExtra(PackageInstaller.EXTRA_RESOLVED_BASE_PATH,
+ mResolvedBaseFile != null ? mResolvedBaseFile.getAbsolutePath() : null);
+ }
sendOnUserActionRequired(mContext, target, sessionId, intent);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a52ed8b..7ab19ff 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1105,8 +1105,9 @@
@Deprecated
@NonNull
public Computer snapshotComputer(boolean allowLiveComputer) {
+ var isHoldingPackageLock = Thread.holdsLock(mLock);
if (allowLiveComputer) {
- if (Thread.holdsLock(mLock)) {
+ if (isHoldingPackageLock) {
// If the current thread holds mLock then it may have modified state but not
// yet invalidated the snapshot. Always give the thread the live computer.
return mLiveComputer;
@@ -1120,6 +1121,15 @@
return oldSnapshot.use();
}
+ if (isHoldingPackageLock) {
+ // If the current thread holds mLock then it already has exclusive write access to the
+ // two snapshot fields, and we can just go ahead and rebuild the snapshot.
+ @SuppressWarnings("GuardedBy")
+ var newSnapshot = rebuildSnapshot(oldSnapshot, pendingVersion);
+ sSnapshot.set(newSnapshot);
+ return newSnapshot.use();
+ }
+
synchronized (mSnapshotLock) {
// Re-capture pending version in case a new invalidation occurred since last check
var rebuildSnapshot = sSnapshot.get();
@@ -1137,7 +1147,11 @@
// Fetch version one last time to ensure that the rebuilt snapshot matches
// the latest invalidation, which could have come in between entering the
// SnapshotLock and mLock sync blocks.
+ rebuildSnapshot = sSnapshot.get();
rebuildVersion = sSnapshotPendingVersion.get();
+ if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
+ return rebuildSnapshot.use();
+ }
// Build the snapshot for this version
var newSnapshot = rebuildSnapshot(rebuildSnapshot, rebuildVersion);
@@ -1147,7 +1161,7 @@
}
}
- @GuardedBy({ "mLock", "mSnapshotLock"})
+ @GuardedBy("mLock")
private Computer rebuildSnapshot(@Nullable Computer oldSnapshot, int newVersion) {
var now = SystemClock.currentTimeMicro();
var hits = oldSnapshot == null ? -1 : oldSnapshot.getUsed();
@@ -2878,7 +2892,7 @@
static void renameStaticSharedLibraryPackage(ParsedPackage parsedPackage) {
// Derive the new package synthetic package name
parsedPackage.setPackageName(toStaticSharedLibraryPackageName(
- parsedPackage.getPackageName(), parsedPackage.getStaticSharedLibVersion()));
+ parsedPackage.getPackageName(), parsedPackage.getStaticSharedLibraryVersion()));
}
private static String toStaticSharedLibraryPackageName(
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index b18179e..433e7a1 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1386,6 +1386,8 @@
return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
}
+
+
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 9b6bfd9..eb99536 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -234,7 +234,7 @@
}
if (pkg.getStaticSharedLibraryName() != null) {
if (mSharedLibraries.removeSharedLibrary(pkg.getStaticSharedLibraryName(),
- pkg.getStaticSharedLibVersion())) {
+ pkg.getStaticSharedLibraryVersion())) {
if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 53be787..ff020eb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1455,7 +1455,7 @@
void checkAndConvertSharedUserSettingsLPw(SharedUserSetting sharedUser) {
if (!sharedUser.isSingleUser()) return;
final AndroidPackage pkg = sharedUser.getPackageSettings().valueAt(0).getPkg();
- if (pkg != null && pkg.isLeavingSharedUid()
+ if (pkg != null && pkg.isLeavingSharedUser()
&& SharedUidMigration.applyStrategy(BEST_EFFORT)) {
convertSharedUserSettingsLPw(sharedUser);
}
@@ -4857,7 +4857,7 @@
pw.print(prefix); pw.println(" static library:");
pw.print(prefix); pw.print(" ");
pw.print("name:"); pw.print(pkg.getStaticSharedLibraryName());
- pw.print(" version:"); pw.println(pkg.getStaticSharedLibVersion());
+ pw.print(" version:"); pw.println(pkg.getStaticSharedLibraryVersion());
}
if (pkg.getSdkLibraryName() != null) {
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 8c2b212..d2ce23e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -408,7 +408,7 @@
final int versionCount = versionedLib.size();
for (int i = 0; i < versionCount; i++) {
final long libVersion = versionedLib.keyAt(i);
- if (libVersion < pkg.getStaticSharedLibVersion()) {
+ if (libVersion < pkg.getStaticSharedLibraryVersion()) {
previousLibVersion = Math.max(previousLibVersion, libVersion);
}
}
@@ -468,7 +468,7 @@
}
} else if (pkg.getStaticSharedLibraryName() != null) {
SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
- pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibVersion());
+ pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibraryVersion());
if (definedLibrary != null) {
action.accept(definedLibrary, libInfo);
}
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index a7a4c4e..a037ae8 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -253,7 +253,7 @@
}
if (mDisabledPackages.size() == 1) {
final AndroidPackage pkg = mDisabledPackages.valueAt(0).getPkg();
- return pkg != null && pkg.isLeavingSharedUid();
+ return pkg != null && pkg.isLeavingSharedUser();
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 87805e0..ff993ea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -131,7 +131,7 @@
info.splitRevisionCodes = pkg.getSplitRevisionCodes();
info.versionName = pkg.getVersionName();
info.sharedUserId = pkg.getSharedUserId();
- info.sharedUserLabel = pkg.getSharedUserLabel();
+ info.sharedUserLabel = pkg.getSharedUserLabelRes();
info.applicationInfo = applicationInfo;
info.installLocation = pkg.getInstallLocation();
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
@@ -218,7 +218,7 @@
}
}
}
- if (pkg.areAttributionsUserVisible()) {
+ if (pkg.isAttributionsUserVisible()) {
info.applicationInfo.privateFlagsExt
|= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
} else {
@@ -869,7 +869,7 @@
public static int appInfoFlags(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
// @formatter:off
int pkgWithoutStateFlags = flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE)
- | flag(pkg.isBaseHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
+ | flag(pkg.isHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
| flag(pkg.isAllowBackup(), ApplicationInfo.FLAG_ALLOW_BACKUP)
| flag(pkg.isKillAfterRestore(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
| flag(pkg.isRestoreAnyVersion(), ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
@@ -972,7 +972,7 @@
// @formatter:off
int pkgWithoutStateFlags = flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
| flag(pkg.hasRequestForegroundServiceExemption(), ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION)
- | flag(pkg.areAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
+ | flag(pkg.isAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
| flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK)
| flag(isAllowlistedForHiddenApis, ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS);
return appInfoPrivateFlagsExt(pkgWithoutStateFlags, pkgSetting);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 4fee84f..f3ee531 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -103,7 +103,7 @@
return new SharedLibraryInfo(null, pkg.getPackageName(),
AndroidPackageUtils.getAllCodePaths(pkg),
pkg.getStaticSharedLibraryName(),
- pkg.getStaticSharedLibVersion(),
+ pkg.getStaticSharedLibraryVersion(),
SharedLibraryInfo.TYPE_STATIC,
new VersionedPackage(pkg.getManifestPackageName(),
pkg.getLongVersionCode()),
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index ba36ab7..e361c93 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -717,7 +717,7 @@
}
@Override
- public boolean areAttributionsUserVisible() {
+ public boolean isAttributionsUserVisible() {
return getBoolean(Booleans.ATTRIBUTIONS_ARE_USER_VISIBLE);
}
@@ -869,7 +869,7 @@
}
@Override
- public int getBanner() {
+ public int getBannerRes() {
return banner;
}
@@ -897,7 +897,7 @@
@Nullable
@Override
- public String getClassName() {
+ public String getApplicationClassName() {
return className;
}
@@ -924,7 +924,7 @@
}
@Override
- public int getDataExtractionRules() {
+ public int getDataExtractionRulesRes() {
return dataExtractionRules;
}
@@ -940,7 +940,7 @@
}
@Override
- public int getFullBackupContent() {
+ public int getFullBackupContentRes() {
return fullBackupContent;
}
@@ -1006,7 +1006,7 @@
}
@Override
- public int getLogo() {
+ public int getLogoRes() {
return logo;
}
@@ -1277,7 +1277,7 @@
}
@Override
- public int getSharedUserLabel() {
+ public int getSharedUserLabelRes() {
return sharedUserLabel;
}
@@ -1330,7 +1330,7 @@
}
@Override
- public long getStaticSharedLibVersion() {
+ public long getStaticSharedLibraryVersion() {
return staticSharedLibVersion;
}
@@ -1356,7 +1356,7 @@
}
@Override
- public int getTheme() {
+ public int getThemeRes() {
return theme;
}
@@ -1519,8 +1519,8 @@
}
@Override
- public boolean isBaseHardwareAccelerated() {
- return getBoolean(Booleans.BASE_HARDWARE_ACCELERATED);
+ public boolean isHardwareAccelerated() {
+ return getBoolean(Booleans.HARDWARE_ACCELERATED);
}
@Override
@@ -1609,7 +1609,7 @@
}
@Override
- public boolean isLeavingSharedUid() {
+ public boolean isLeavingSharedUser() {
return getBoolean(Booleans.LEAVING_SHARED_UID);
}
@@ -1840,14 +1840,14 @@
}
@Override
- public PackageImpl setBanner(int value) {
+ public PackageImpl setBannerRes(int value) {
banner = value;
return this;
}
@Override
- public PackageImpl setBaseHardwareAccelerated(boolean value) {
- return setBoolean(Booleans.BASE_HARDWARE_ACCELERATED, value);
+ public PackageImpl setHardwareAccelerated(boolean value) {
+ return setBoolean(Booleans.HARDWARE_ACCELERATED, value);
}
@Override
@@ -1874,7 +1874,7 @@
}
@Override
- public PackageImpl setClassName(@Nullable String className) {
+ public PackageImpl setApplicationClassName(@Nullable String className) {
this.className = className == null ? null : className.trim();
return this;
}
@@ -1903,7 +1903,7 @@
}
@Override
- public PackageImpl setDataExtractionRules(int value) {
+ public PackageImpl setDataExtractionRulesRes(int value) {
dataExtractionRules = value;
return this;
}
@@ -1940,7 +1940,7 @@
}
@Override
- public PackageImpl setFullBackupContent(int value) {
+ public PackageImpl setFullBackupContentRes(int value) {
fullBackupContent = value;
return this;
}
@@ -2022,7 +2022,7 @@
}
@Override
- public PackageImpl setLeavingSharedUid(boolean value) {
+ public PackageImpl setLeavingSharedUser(boolean value) {
return setBoolean(Booleans.LEAVING_SHARED_UID, value);
}
@@ -2033,7 +2033,7 @@
}
@Override
- public PackageImpl setLogo(int value) {
+ public PackageImpl setLogoRes(int value) {
logo = value;
return this;
}
@@ -2292,7 +2292,7 @@
}
@Override
- public PackageImpl setSharedUserLabel(int value) {
+ public PackageImpl setSharedUserLabelRes(int value) {
sharedUserLabel = value;
return this;
}
@@ -2318,7 +2318,7 @@
}
@Override
- public PackageImpl setStaticSharedLibVersion(long value) {
+ public PackageImpl setStaticSharedLibraryVersion(long value) {
staticSharedLibVersion = value;
return this;
}
@@ -2397,7 +2397,7 @@
}
@Override
- public PackageImpl setTheme(int value) {
+ public PackageImpl setThemeRes(int value) {
theme = value;
return this;
}
@@ -3529,7 +3529,7 @@
private static class Booleans {
@LongDef({
EXTERNAL_STORAGE,
- BASE_HARDWARE_ACCELERATED,
+ HARDWARE_ACCELERATED,
ALLOW_BACKUP,
KILL_AFTER_RESTORE,
RESTORE_ANY_VERSION,
@@ -3593,7 +3593,7 @@
public @interface Flags {}
private static final long EXTERNAL_STORAGE = 1L;
- private static final long BASE_HARDWARE_ACCELERATED = 1L << 1;
+ private static final long HARDWARE_ACCELERATED = 1L << 1;
private static final long ALLOW_BACKUP = 1L << 2;
private static final long KILL_AFTER_RESTORE = 1L << 3;
private static final long RESTORE_ANY_VERSION = 1L << 4;
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 075173d..49f85e9 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -16,9 +16,14 @@
package com.android.server.pm.pkg;
+import android.annotation.Dimension;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
import android.annotation.SystemApi;
+import android.annotation.XmlRes;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -86,6 +91,109 @@
public interface AndroidPackage {
/**
+ * @see ApplicationInfo#className
+ * @see R.styleable#AndroidManifestApplication_name
+ */
+ @Nullable
+ String getApplicationClassName();
+
+ /**
+ * @see ApplicationInfo#appComponentFactory
+ * @see R.styleable#AndroidManifestApplication_appComponentFactory
+ */
+ @Nullable
+ String getAppComponentFactory();
+
+ /**
+ * @see ApplicationInfo#backupAgentName
+ * @see R.styleable#AndroidManifestApplication_backupAgent
+ */
+ @Nullable
+ String getBackupAgentName();
+
+ /**
+ * @see ApplicationInfo#banner
+ * @see R.styleable#AndroidManifestApplication_banner
+ */
+ @DrawableRes
+ int getBannerRes();
+
+ /**
+ * @see PackageInfo#baseRevisionCode
+ * @see R.styleable#AndroidManifest_revisionCode
+ */
+ int getBaseRevisionCode();
+
+ /**
+ * @see ApplicationInfo#category
+ * @see R.styleable#AndroidManifestApplication_appCategory
+ */
+ int getCategory();
+
+ /**
+ * @see ApplicationInfo#classLoaderName
+ * @see R.styleable#AndroidManifestApplication_classLoader
+ */
+ @Nullable
+ String getClassLoaderName();
+
+ /**
+ * @see ApplicationInfo#compatibleWidthLimitDp
+ * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
+ */
+ @Dimension(unit = Dimension.DP)
+ int getCompatibleWidthLimitDp();
+
+ /**
+ * @see ApplicationInfo#dataExtractionRulesRes
+ * @see R.styleable#AndroidManifestApplication_dataExtractionRules
+ */
+ @XmlRes
+ int getDataExtractionRulesRes();
+
+ /**
+ * @see ApplicationInfo#descriptionRes
+ * @see R.styleable#AndroidManifestApplication_description
+ */
+ @StringRes // This is actually format="reference"
+ int getDescriptionRes();
+
+ /**
+ * @see ApplicationInfo#fullBackupContent
+ * @see R.styleable#AndroidManifestApplication_fullBackupContent
+ */
+ @XmlRes
+ int getFullBackupContentRes();
+
+ /**
+ * @see ApplicationInfo#getGwpAsanMode()
+ * @see R.styleable#AndroidManifestApplication_gwpAsanMode
+ */
+ @ApplicationInfo.GwpAsanMode
+ int getGwpAsanMode();
+
+ /**
+ * @see ApplicationInfo#iconRes
+ * @see R.styleable#AndroidManifestApplication_icon
+ */
+ @DrawableRes
+ int getIconRes();
+
+ /**
+ * @see ApplicationInfo#labelRes
+ * @see R.styleable#AndroidManifestApplication_label
+ */
+ @StringRes
+ int getLabelRes();
+
+ /**
+ * @see ApplicationInfo#largestWidthLimitDp
+ * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
+ */
+ @Dimension(unit = Dimension.DP)
+ int getLargestWidthLimitDp();
+
+ /**
* Library names this package is declared as, for use by other packages with "uses-library".
*
* @see R.styleable#AndroidManifestLibrary
@@ -94,12 +202,104 @@
List<String> getLibraryNames();
/**
+ * @see ApplicationInfo#logo
+ * @see R.styleable#AndroidManifestApplication_logo
+ */
+ @DrawableRes
+ int getLogoRes();
+
+ /**
+ * The resource ID used to provide the application's locales configuration.
+ *
+ * @see R.styleable#AndroidManifestApplication_localeConfig
+ */
+ @XmlRes
+ int getLocaleConfigRes();
+
+ /**
+ * @see PackageInfo#getLongVersionCode()
+ * @see R.styleable#AndroidManifest_versionCode
+ * @see R.styleable#AndroidManifest_versionCodeMajor
+ */
+ long getLongVersionCode();
+
+ /**
+ * @see ApplicationInfo#maxAspectRatio
+ * @see R.styleable#AndroidManifestApplication_maxAspectRatio
+ */
+ float getMaxAspectRatio();
+
+ /**
+ * @see ApplicationInfo#minAspectRatio
+ * @see R.styleable#AndroidManifestApplication_minAspectRatio
+ */
+ float getMinAspectRatio();
+
+ /**
+ * @see ApplicationInfo#getNativeHeapZeroInitialized()
+ * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
+ */
+ @ApplicationInfo.NativeHeapZeroInitialized
+ int getNativeHeapZeroInitialized();
+
+ /**
+ * @see ApplicationInfo#networkSecurityConfigRes
+ * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
+ */
+ @XmlRes
+ int getNetworkSecurityConfigRes();
+
+ /**
+ * @see PackageInfo#requiredAccountType
+ * @see R.styleable#AndroidManifestApplication_requiredAccountType
+ */
+ @Nullable
+ String getRequiredAccountType();
+
+ /**
+ * @see ApplicationInfo#requiresSmallestWidthDp
+ * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
+ */
+ @Dimension(unit = Dimension.DP)
+ int getRequiresSmallestWidthDp();
+
+ /**
+ * The restricted account authenticator type that is used by this application.
+ *
+ * @see PackageInfo#restrictedAccountType
+ * @see R.styleable#AndroidManifestApplication_restrictedAccountType
+ */
+ @Nullable
+ String getRestrictedAccountType();
+
+ /**
+ * @see ApplicationInfo#roundIconRes
+ * @see R.styleable#AndroidManifestApplication_roundIcon
+ */
+ @DrawableRes
+ int getRoundIconRes();
+
+ /**
* @see R.styleable#AndroidManifestSdkLibrary_name
*/
@Nullable
String getSdkLibraryName();
/**
+ * @see PackageInfo#sharedUserId
+ * @see R.styleable#AndroidManifest_sharedUserId
+ */
+ @Nullable
+ String getSharedUserId();
+
+ /**
+ * @see PackageInfo#sharedUserLabel
+ * @see R.styleable#AndroidManifest_sharedUserLabel
+ */
+ @StringRes
+ int getSharedUserLabelRes();
+
+ /**
* @return List of all splits for a package. Note that base.apk is considered a
* split and will be provided as index 0 of the list.
*/
@@ -113,6 +313,12 @@
String getStaticSharedLibraryName();
/**
+ * @see R.styleable#AndroidManifestStaticLibrary_version
+ * @hide
+ */
+ long getStaticSharedLibraryVersion();
+
+ /**
* @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
* package was installed.
*/
@@ -126,23 +332,319 @@
int getTargetSdkVersion();
/**
+ * @see ApplicationInfo#theme
+ * @see R.styleable#AndroidManifestApplication_theme
+ */
+ @StyleRes
+ int getThemeRes();
+
+ /**
+ * @see ApplicationInfo#uiOptions
+ * @see R.styleable#AndroidManifestApplication_uiOptions
+ */
+ int getUiOptions();
+
+ /**
+ * @see PackageInfo#versionName
+ */
+ @Nullable
+ String getVersionName();
+
+ /**
+ * @see ApplicationInfo#zygotePreloadName
+ * @see R.styleable#AndroidManifestApplication_zygotePreloadName
+ */
+ @Nullable
+ String getZygotePreloadName();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
+ * @see R.styleable#AndroidManifestApplication_allowAudioPlaybackCapture
+ */
+ boolean isAllowAudioPlaybackCapture();
+
+ /**
+ * @see ApplicationInfo#FLAG_ALLOW_BACKUP
+ * @see R.styleable#AndroidManifestApplication_allowBackup
+ */
+ boolean isAllowBackup();
+
+ /**
+ * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
+ * @see R.styleable#AndroidManifestApplication_allowClearUserData
+ */
+ boolean isAllowClearUserData();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
+ * @see R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore
+ */
+ boolean isAllowClearUserDataOnFailedRestore();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
+ * @see R.styleable#AndroidManifestApplication_allowNativeHeapPointerTagging
+ */
+ boolean isAllowNativeHeapPointerTagging();
+
+ /**
+ * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
+ * @see R.styleable#AndroidManifestApplication_allowTaskReparenting
+ */
+ boolean isAllowTaskReparenting();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+ * android.os.Build.VERSION_CODES#DONUT}.
+ *
+ * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
+ * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
+ */
+ boolean isAnyDensity();
+
+ /**
+ * @see ApplicationInfo#areAttributionsUserVisible()
+ * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible
+ */
+ boolean isAttributionsUserVisible();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND
+ * @see R.styleable#AndroidManifestApplication_backupInForeground
+ */
+ boolean isBackupInForeground();
+
+ /**
+ * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED
+ * @see R.styleable#AndroidManifestApplication_hardwareAccelerated
+ */
+ boolean isHardwareAccelerated();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
+ * @see R.styleable#AndroidManifestApplication_cantSaveState
+ */
+ boolean isCantSaveState();
+
+ /**
+ * @see PackageInfo#coreApp
+ */
+ boolean isCoreApp();
+
+ /**
+ * @see ApplicationInfo#crossProfile
+ * @see R.styleable#AndroidManifestApplication_crossProfile
+ */
+ boolean isCrossProfile();
+
+ /**
* @see ApplicationInfo#FLAG_DEBUGGABLE
* @see R.styleable#AndroidManifestApplication_debuggable
*/
boolean isDebuggable();
/**
+ * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
+ * @see R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage
+ */
+ boolean isDefaultToDeviceProtectedStorage();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE
+ * @see R.styleable#AndroidManifestApplication_directBootAware
+ */
+ boolean isDirectBootAware();
+
+ /**
+ * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
+ * @see R.styleable#AndroidManifestApplication_extractNativeLibs
+ */
+ boolean isExtractNativeLibs();
+
+ /**
+ * @see ApplicationInfo#FLAG_FACTORY_TEST
+ */
+ boolean isFactoryTest();
+
+ /**
+ * @see R.styleable#AndroidManifestApplication_forceQueryable
+ */
+ boolean isForceQueryable();
+
+ /**
+ * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY
+ * @see R.styleable#AndroidManifestApplication_fullBackupOnly
+ */
+ boolean isFullBackupOnly();
+
+ /**
+ * @see ApplicationInfo#FLAG_HAS_CODE
+ * @see R.styleable#AndroidManifestApplication_hasCode
+ */
+ boolean isHasCode();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
+ * @see R.styleable#AndroidManifestApplication_hasFragileUserData
+ */
+ boolean isHasFragileUserData();
+
+ /**
* @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
* @see R.styleable#AndroidManifest_isolatedSplits
*/
boolean isIsolatedSplitLoading();
/**
+ * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
+ * @see R.styleable#AndroidManifestApplication_killAfterRestore
+ */
+ boolean isKillAfterRestore();
+
+ /**
+ * @see ApplicationInfo#FLAG_LARGE_HEAP
+ * @see R.styleable#AndroidManifestApplication_largeHeap
+ */
+ boolean isLargeHeap();
+
+ /**
+ * Returns true if R.styleable#AndroidManifest_sharedUserMaxSdkVersion is set to a value
+ * smaller than the current SDK version, indicating the package wants to leave its declared
+ * {@link #getSharedUserId()}. This only occurs on new installs, pretending the app never
+ * declared one.
+ *
+ * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
+ */
+ boolean isLeavingSharedUser();
+
+ /**
+ * @see ApplicationInfo#FLAG_MULTIARCH
+ * @see R.styleable#AndroidManifestApplication_multiArch
+ */
+ boolean isMultiArch();
+
+ /**
+ * @see ApplicationInfo#nativeLibraryRootRequiresIsa
+ */
+ boolean isNativeLibraryRootRequiresIsa();
+
+ /**
+ * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
+ */
+ boolean isOnBackInvokedCallbackEnabled();
+
+ /**
+ * @see ApplicationInfo#FLAG_PERSISTENT
+ * @see R.styleable#AndroidManifestApplication_persistent
+ */
+ boolean isPersistent();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE
+ * @see R.styleable#AndroidManifestProfileable
+ */
+ boolean isProfileable();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL
+ * @see R.styleable#AndroidManifestProfileable_shell
+ */
+ boolean isProfileableByShell();
+
+ /**
+ * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE
+ * @see R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
+ */
+ boolean isRequestLegacyExternalStorage();
+
+ /**
+ * @see PackageInfo#requiredForAllUsers
+ * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
+ */
+ boolean isRequiredForAllUsers();
+
+ /**
+ * Whether the enabled settings of components in the application should be reset to the default,
+ * when the application's user data is cleared.
+ *
+ * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
+ */
+ boolean isResetEnabledSettingsOnAppDataCleared();
+
+ /**
+ * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION
+ * @see R.styleable#AndroidManifestApplication_restoreAnyVersion
+ */
+ boolean isRestoreAnyVersion();
+
+ /**
* @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
*/
boolean isSignedWithPlatformKey();
/**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+ * android.os.Build.VERSION_CODES#GINGERBREAD}.
+ *
+ * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
+ */
+ boolean isSupportsExtraLargeScreens();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+ * android.os.Build.VERSION_CODES#DONUT}.
+ *
+ * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
+ */
+ boolean isSupportsLargeScreens();
+
+ /**
+ * If omitted from manifest, returns true.
+ *
+ * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
+ */
+ boolean isSupportsNormalScreens();
+
+ /**
+ * @see ApplicationInfo#FLAG_SUPPORTS_RTL
+ * @see R.styleable#AndroidManifestApplication_supportsRtl
+ */
+ boolean isSupportsRtl();
+
+ /**
+ * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+ * android.os.Build.VERSION_CODES#DONUT}.
+ *
+ * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
+ * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
+ */
+ boolean isSupportsSmallScreens();
+
+ /**
+ * @see ApplicationInfo#FLAG_TEST_ONLY
+ * @see R.styleable#AndroidManifestApplication_testOnly
+ */
+ boolean isTestOnly();
+
+ /**
+ * The install time abi override to choose 32bit abi's when multiple abi's are present. This is
+ * only meaningful for multiarch applications. The use32bitAbi attribute is ignored if
+ * cpuAbiOverride is also set.
+ *
+ * @see R.attr#use32bitAbi
+ */
+ boolean isUse32BitAbi();
+
+ /**
+ * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
+ * @see R.styleable#AndroidManifestApplication_usesCleartextTraffic
+ */
+ boolean isUsesCleartextTraffic();
+
+ /**
* @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
* @see R.styleable#AndroidManifestApplication_useEmbeddedDex
*/
@@ -163,14 +665,6 @@
// Methods below this comment are not yet exposed as API
/**
- * @see ApplicationInfo#areAttributionsUserVisible()
- * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible
- * @hide
- */
- @Nullable
- boolean areAttributionsUserVisible();
-
- /**
* Set of Activities parsed from the manifest.
* <p>
* This contains minimal system state and does not
@@ -207,14 +701,6 @@
List<ParsedApexSystemService> getApexSystemServices();
/**
- * @see ApplicationInfo#appComponentFactory
- * @see R.styleable#AndroidManifestApplication_appComponentFactory
- * @hide
- */
- @Nullable
- String getAppComponentFactory();
-
- /**
* @see R.styleable#AndroidManifestAttribution
* @hide
*/
@@ -232,21 +718,6 @@
int getAutoRevokePermissions();
/**
- * @see ApplicationInfo#backupAgentName
- * @see R.styleable#AndroidManifestApplication_backupAgent
- * @hide
- */
- @Nullable
- String getBackupAgentName();
-
- /**
- * @see ApplicationInfo#banner
- * @see R.styleable#AndroidManifestApplication_banner
- * @hide
- */
- int getBanner();
-
- /**
* @see ApplicationInfo#sourceDir
* @see ApplicationInfo#getBaseCodePath
*
@@ -259,43 +730,6 @@
String getBaseApkPath();
/**
- * @see PackageInfo#baseRevisionCode
- * @see R.styleable#AndroidManifest_revisionCode
- * @hide
- */
- int getBaseRevisionCode();
-
- /**
- * @see ApplicationInfo#category
- * @see R.styleable#AndroidManifestApplication_appCategory
- * @hide
- */
- int getCategory();
-
- /**
- * @see ApplicationInfo#classLoaderName
- * @see R.styleable#AndroidManifestApplication_classLoader
- * @hide
- */
- @Nullable
- String getClassLoaderName();
-
- /**
- * @see ApplicationInfo#className
- * @see R.styleable#AndroidManifestApplication_name
- * @hide
- */
- @Nullable
- String getClassName();
-
- /**
- * @see ApplicationInfo#compatibleWidthLimitDp
- * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
- * @hide
- */
- int getCompatibleWidthLimitDp();
-
- /**
* @see ApplicationInfo#compileSdkVersion
* @see R.styleable#AndroidManifest_compileSdkVersion
* @hide
@@ -320,20 +754,6 @@
List<ConfigurationInfo> getConfigPreferences();
/**
- * @see ApplicationInfo#dataExtractionRulesRes
- * @see R.styleable#AndroidManifestApplication_dataExtractionRules
- * @hide
- */
- int getDataExtractionRules();
-
- /**
- * @see ApplicationInfo#descriptionRes
- * @see R.styleable#AndroidManifestApplication_description
- * @hide
- */
- int getDescriptionRes();
-
- /**
* @see PackageInfo#featureGroups
* @see R.styleable#AndroidManifestUsesFeature
* @hide
@@ -343,28 +763,6 @@
List<FeatureGroupInfo> getFeatureGroups();
/**
- * @see ApplicationInfo#fullBackupContent
- * @see R.styleable#AndroidManifestApplication_fullBackupContent
- * @hide
- */
- int getFullBackupContent();
-
- /**
- * @see ApplicationInfo#getGwpAsanMode()
- * @see R.styleable#AndroidManifestApplication_gwpAsanMode
- * @hide
- */
- @ApplicationInfo.GwpAsanMode
- int getGwpAsanMode();
-
- /**
- * @see ApplicationInfo#iconRes
- * @see R.styleable#AndroidManifestApplication_icon
- * @hide
- */
- int getIconRes();
-
- /**
* Permissions requested but not in the manifest. These may have been split or migrated from
* previous versions/definitions.
* @hide
@@ -411,43 +809,6 @@
Set<String> getKnownActivityEmbeddingCerts();
/**
- * @see ApplicationInfo#labelRes
- * @see R.styleable#AndroidManifestApplication_label
- * @hide
- */
- int getLabelRes();
-
- /**
- * @see ApplicationInfo#largestWidthLimitDp
- * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
- * @hide
- */
- int getLargestWidthLimitDp();
-
- /**
- * The resource ID used to provide the application's locales configuration.
- *
- * @see R.styleable#AndroidManifestApplication_localeConfig
- * @hide
- */
- int getLocaleConfigRes();
-
- /**
- * @see ApplicationInfo#logo
- * @see R.styleable#AndroidManifestApplication_logo
- * @hide
- */
- int getLogo();
-
- /**
- * @see PackageInfo#getLongVersionCode()
- * @see R.styleable#AndroidManifest_versionCode
- * @see R.styleable#AndroidManifest_versionCodeMajor
- * @hide
- */
- long getLongVersionCode();
-
- /**
* @see ApplicationInfo#manageSpaceActivityName
* @see R.styleable#AndroidManifestApplication_manageSpaceActivity
* @hide
@@ -464,13 +825,6 @@
String getManifestPackageName();
/**
- * @see ApplicationInfo#maxAspectRatio
- * @see R.styleable#AndroidManifestApplication_maxAspectRatio
- * @hide
- */
- float getMaxAspectRatio();
-
- /**
* @see R.styleable#AndroidManifestUsesSdk_maxSdkVersion
* @hide
*/
@@ -501,13 +855,6 @@
Set<String> getMimeGroups();
/**
- * @see ApplicationInfo#minAspectRatio
- * @see R.styleable#AndroidManifestApplication_minAspectRatio
- * @hide
- */
- float getMinAspectRatio();
-
- /**
* @see R.styleable#AndroidManifestExtensionSdk
* @hide
*/
@@ -523,14 +870,6 @@
int getMinSdkVersion();
/**
- * @see ApplicationInfo#getNativeHeapZeroInitialized()
- * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
- * @hide
- */
- @ApplicationInfo.NativeHeapZeroInitialized
- int getNativeHeapZeroInitialized();
-
- /**
* @see ApplicationInfo#nativeLibraryDir
* @hide
*/
@@ -545,13 +884,6 @@
String getNativeLibraryRootDir();
/**
- * @see ApplicationInfo#networkSecurityConfigRes
- * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
- * @hide
- */
- int getNetworkSecurityConfigRes();
-
- /**
* If {@link R.styleable#AndroidManifestApplication_label} is a string literal, this is it.
* Otherwise, it's stored as {@link #getLabelRes()}.
*
@@ -787,21 +1119,6 @@
List<String> getRequestedPermissions();
/**
- * @see PackageInfo#requiredAccountType
- * @see R.styleable#AndroidManifestApplication_requiredAccountType
- * @hide
- */
- @Nullable
- String getRequiredAccountType();
-
- /**
- * @see ApplicationInfo#requiresSmallestWidthDp
- * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
- * @hide
- */
- int getRequiresSmallestWidthDp();
-
- /**
* Whether or not the app requested explicitly resizeable Activities. Null value means nothing
* was explicitly requested.
*
@@ -824,23 +1141,6 @@
byte[] getRestrictUpdateHash();
/**
- * The restricted account authenticator type that is used by this application.
- *
- * @see PackageInfo#restrictedAccountType
- * @see R.styleable#AndroidManifestApplication_restrictedAccountType
- * @hide
- */
- @Nullable
- String getRestrictedAccountType();
-
- /**
- * @see ApplicationInfo#roundIconRes
- * @see R.styleable#AndroidManifestApplication_roundIcon
- * @hide
- */
- int getRoundIconRes();
-
- /**
* @see R.styleable#AndroidManifestSdkLibrary_versionMajor
* @hide
*/
@@ -872,21 +1172,6 @@
List<ParsedService> getServices();
/**
- * @see PackageInfo#sharedUserId
- * @see R.styleable#AndroidManifest_sharedUserId
- * @hide
- */
- @Nullable
- String getSharedUserId();
-
- /**
- * @see PackageInfo#sharedUserLabel
- * @see R.styleable#AndroidManifest_sharedUserLabel
- * @hide
- */
- int getSharedUserLabel();
-
- /**
* The signature data of all APKs in this package, which must be exactly the same across the
* base and splits.
* @hide
@@ -949,12 +1234,6 @@
int[] getSplitRevisionCodes();
/**
- * @see R.styleable#AndroidManifestStaticLibrary_version
- * @hide
- */
- long getStaticSharedLibVersion();
-
- /**
* @see ApplicationInfo#targetSandboxVersion
* @see R.styleable#AndroidManifest_targetSandboxVersion
* @hide
@@ -970,20 +1249,6 @@
String getTaskAffinity();
/**
- * @see ApplicationInfo#theme
- * @see R.styleable#AndroidManifestApplication_theme
- * @hide
- */
- int getTheme();
-
- /**
- * @see ApplicationInfo#uiOptions
- * @see R.styleable#AndroidManifestApplication_uiOptions
- * @hide
- */
- int getUiOptions();
-
- /**
* This is an appId, the {@link ApplicationInfo#uid} if the user ID is
* {@link android.os.UserHandle#SYSTEM}.
*
@@ -1095,27 +1360,12 @@
long[] getUsesStaticLibrariesVersions();
/**
- * @see PackageInfo#versionName
- * @hide
- */
- @Nullable
- String getVersionName();
-
- /**
* @see ApplicationInfo#volumeUuid
* @hide
*/
@Nullable
String getVolumeUuid();
- /**
- * @see ApplicationInfo#zygotePreloadName
- * @see R.styleable#AndroidManifestApplication_zygotePreloadName
- * @hide
- */
- @Nullable
- String getZygotePreloadName();
-
/** @hide */
boolean hasPreserveLegacyExternalStorage();
@@ -1133,110 +1383,10 @@
*/
Boolean hasRequestRawExternalStorageAccess();
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
- * @see R.styleable#AndroidManifestApplication_allowAudioPlaybackCapture
- * @hide
- */
- boolean isAllowAudioPlaybackCapture();
-
- /**
- * @see ApplicationInfo#FLAG_ALLOW_BACKUP
- * @see R.styleable#AndroidManifestApplication_allowBackup
- * @hide
- */
- boolean isAllowBackup();
-
- /**
- * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
- * @see R.styleable#AndroidManifestApplication_allowClearUserData
- * @hide
- */
- boolean isAllowClearUserData();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
- * @see R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore
- * @hide
- */
- boolean isAllowClearUserDataOnFailedRestore();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
- * @see R.styleable#AndroidManifestApplication_allowNativeHeapPointerTagging
- * @hide
- */
- boolean isAllowNativeHeapPointerTagging();
-
- /**
- * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
- * @see R.styleable#AndroidManifestApplication_allowTaskReparenting
- * @hide
- */
- boolean isAllowTaskReparenting();
-
- /**
- * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
- * android.os.Build.VERSION_CODES#DONUT}.
- *
- * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
- * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
- * @hide
- */
- boolean isAnyDensity();
-
/** @hide */
boolean isApex();
/**
- * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND
- * @see R.styleable#AndroidManifestApplication_backupInForeground
- * @hide
- */
- boolean isBackupInForeground();
-
- /**
- * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED
- * @see R.styleable#AndroidManifestApplication_hardwareAccelerated
- * @hide
- */
- boolean isBaseHardwareAccelerated();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
- * @see R.styleable#AndroidManifestApplication_cantSaveState
- * @hide
- */
- boolean isCantSaveState();
-
- /**
- * @see PackageInfo#coreApp
- * @hide
- */
- boolean isCoreApp();
-
- /**
- * @see ApplicationInfo#crossProfile
- * @see R.styleable#AndroidManifestApplication_crossProfile
- * @hide
- */
- boolean isCrossProfile();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
- * @see R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage
- * @hide
- */
- boolean isDefaultToDeviceProtectedStorage();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE
- * @see R.styleable#AndroidManifestApplication_directBootAware
- * @hide
- */
- boolean isDirectBootAware();
-
- /**
* @see ApplicationInfo#enabled
* @see R.styleable#AndroidManifestApplication_enabled
* @hide
@@ -1250,32 +1400,6 @@
boolean isExternalStorage();
/**
- * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
- * @see R.styleable#AndroidManifestApplication_extractNativeLibs
- * @hide
- */
- boolean isExtractNativeLibs();
-
- /**
- * @see ApplicationInfo#FLAG_FACTORY_TEST
- * @hide
- */
- boolean isFactoryTest();
-
- /**
- * @see R.styleable#AndroidManifestApplication_forceQueryable
- * @hide
- */
- boolean isForceQueryable();
-
- /**
- * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY
- * @see R.styleable#AndroidManifestApplication_fullBackupOnly
- * @hide
- */
- boolean isFullBackupOnly();
-
- /**
* @see ApplicationInfo#FLAG_IS_GAME
* @see R.styleable#AndroidManifestApplication_isGame
* @hide
@@ -1284,13 +1408,6 @@
boolean isGame();
/**
- * @see ApplicationInfo#FLAG_HAS_CODE
- * @see R.styleable#AndroidManifestApplication_hasCode
- * @hide
- */
- boolean isHasCode();
-
- /**
* @see ApplicationInfo#PRIVATE_FLAG_HAS_DOMAIN_URLS
* @see R.styleable#AndroidManifestIntentFilter
* @hide
@@ -1298,55 +1415,6 @@
boolean isHasDomainUrls();
/**
- * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
- * @see R.styleable#AndroidManifestApplication_hasFragileUserData
- * @hide
- */
- boolean isHasFragileUserData();
-
- /**
- * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
- * @see R.styleable#AndroidManifestApplication_killAfterRestore
- * @hide
- */
- boolean isKillAfterRestore();
-
- /**
- * @see ApplicationInfo#FLAG_LARGE_HEAP
- * @see R.styleable#AndroidManifestApplication_largeHeap
- * @hide
- */
- boolean isLargeHeap();
-
- /**
- * Returns true if R.styleable#AndroidManifest_sharedUserMaxSdkVersion is set to a value
- * smaller than the current SDK version.
- *
- * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
- * @hide
- */
- boolean isLeavingSharedUid();
-
- /**
- * @see ApplicationInfo#FLAG_MULTIARCH
- * @see R.styleable#AndroidManifestApplication_multiArch
- * @hide
- */
- boolean isMultiArch();
-
- /**
- * @see ApplicationInfo#nativeLibraryRootRequiresIsa
- * @hide
- */
- boolean isNativeLibraryRootRequiresIsa();
-
- /**
- * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
- * @hide
- */
- boolean isOnBackInvokedCallbackEnabled();
-
- /**
* @see ApplicationInfo#PRIVATE_FLAG_IS_RESOURCE_OVERLAY
* @see ApplicationInfo#isResourceOverlay()
* @see R.styleable#AndroidManifestResourceOverlay
@@ -1371,50 +1439,6 @@
boolean isPartiallyDirectBootAware();
/**
- * @see ApplicationInfo#FLAG_PERSISTENT
- * @see R.styleable#AndroidManifestApplication_persistent
- * @hide
- */
- boolean isPersistent();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE
- * @see R.styleable#AndroidManifestProfileable
- * @hide
- */
- boolean isProfileable();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL
- * @see R.styleable#AndroidManifestProfileable_shell
- * @hide
- */
- boolean isProfileableByShell();
-
- /**
- * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE
- * @see R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
- * @hide
- */
- boolean isRequestLegacyExternalStorage();
-
- /**
- * @see PackageInfo#requiredForAllUsers
- * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
- * @hide
- */
- boolean isRequiredForAllUsers();
-
- /**
- * Whether the enabled settings of components in the application should be reset to the default,
- * when the application's user data is cleared.
- *
- * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
- * @hide
- */
- boolean isResetEnabledSettingsOnAppDataCleared();
-
- /**
* If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
* android.os.Build.VERSION_CODES#DONUT}.
*
@@ -1432,13 +1456,6 @@
boolean isResizeableActivityViaSdkVersion();
/**
- * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION
- * @see R.styleable#AndroidManifestApplication_restoreAnyVersion
- * @hide
- */
- boolean isRestoreAnyVersion();
-
- /**
* True means that this package/app contains an SDK library.
* @see R.styleable#AndroidManifestSdkLibrary
* @hide
@@ -1459,76 +1476,6 @@
boolean isStub();
/**
- * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
- * android.os.Build.VERSION_CODES#GINGERBREAD}.
- *
- * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
- * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
- * @hide
- */
- boolean isSupportsExtraLargeScreens();
-
- /**
- * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
- * android.os.Build.VERSION_CODES#DONUT}.
- *
- * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
- * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
- * @hide
- */
- boolean isSupportsLargeScreens();
-
- /**
- * If omitted from manifest, returns true.
- *
- * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
- * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
- * @hide
- */
- boolean isSupportsNormalScreens();
-
- /**
- * @see ApplicationInfo#FLAG_SUPPORTS_RTL
- * @see R.styleable#AndroidManifestApplication_supportsRtl
- * @hide
- */
- boolean isSupportsRtl();
-
- /**
- * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
- * android.os.Build.VERSION_CODES#DONUT}.
- *
- * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
- * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
- * @hide
- */
- boolean isSupportsSmallScreens();
-
- /**
- * @see ApplicationInfo#FLAG_TEST_ONLY
- * @see R.styleable#AndroidManifestApplication_testOnly
- * @hide
- */
- boolean isTestOnly();
-
- /**
- * The install time abi override to choose 32bit abi's when multiple abi's are present. This is
- * only meaningful for multiarch applications. The use32bitAbi attribute is ignored if
- * cpuAbiOverride is also set.
- *
- * @see R.attr#use32bitAbi
- * @hide
- */
- boolean isUse32BitAbi();
-
- /**
- * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
- * @see R.styleable#AndroidManifestApplication_usesCleartextTraffic
- * @hide
- */
- boolean isUsesCleartextTraffic();
-
- /**
* Set if the any of components are visible to instant applications.
*
* @see R.styleable#AndroidManifestActivity_visibleToInstantApps
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index ea791e1..3bd0e0d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -144,7 +144,7 @@
| flag(ActivityInfo.FLAG_SYSTEM_USER_ONLY, R.styleable.AndroidManifestActivity_systemUserOnly, sa)));
if (!receiver) {
- activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isBaseHardwareAccelerated(), sa)
+ activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isHardwareAccelerated(), sa)
| flag(ActivityInfo.FLAG_ALLOW_EMBEDDED, R.styleable.AndroidManifestActivity_allowEmbedded, sa)
| flag(ActivityInfo.FLAG_ALWAYS_FOCUSABLE, R.styleable.AndroidManifestActivity_alwaysFocusable, sa)
| flag(ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS, R.styleable.AndroidManifestActivity_autoRemoveFromRecents, sa)
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 16f5d16..12dfef4 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -155,7 +155,7 @@
ParsingPackage setUiOptions(int uiOptions);
- ParsingPackage setBaseHardwareAccelerated(boolean baseHardwareAccelerated);
+ ParsingPackage setHardwareAccelerated(boolean hardwareAccelerated);
ParsingPackage setResizeableActivity(Boolean resizeable);
@@ -255,13 +255,13 @@
ParsingPackage setBackupAgentName(String backupAgentName);
- ParsingPackage setBanner(int banner);
+ ParsingPackage setBannerRes(int banner);
ParsingPackage setCategory(int category);
ParsingPackage setClassLoaderName(String classLoaderName);
- ParsingPackage setClassName(String className);
+ ParsingPackage setApplicationClassName(String className);
ParsingPackage setCompatibleWidthLimitDp(int compatibleWidthLimitDp);
@@ -281,9 +281,9 @@
ParsingPackage setCrossProfile(boolean crossProfile);
- ParsingPackage setFullBackupContent(int fullBackupContent);
+ ParsingPackage setFullBackupContentRes(int fullBackupContentRes);
- ParsingPackage setDataExtractionRules(int dataExtractionRules);
+ ParsingPackage setDataExtractionRulesRes(int dataExtractionRulesRes);
ParsingPackage setHasDomainUrls(boolean hasDomainUrls);
@@ -292,13 +292,13 @@
ParsingPackage setInstallLocation(int installLocation);
/** @see R#styleable.AndroidManifest_sharedUserMaxSdkVersion */
- ParsingPackage setLeavingSharedUid(boolean leavingSharedUid);
+ ParsingPackage setLeavingSharedUser(boolean leavingSharedUser);
ParsingPackage setLabelRes(int labelRes);
ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp);
- ParsingPackage setLogo(int logo);
+ ParsingPackage setLogoRes(int logo);
ParsingPackage setManageSpaceActivityName(String manageSpaceActivityName);
@@ -336,13 +336,13 @@
ParsingPackage setRoundIconRes(int roundIconRes);
- ParsingPackage setSharedUserLabel(int sharedUserLabel);
+ ParsingPackage setSharedUserLabelRes(int sharedUserLabelRes);
ParsingPackage setSigningDetails(@NonNull SigningDetails signingDetails);
ParsingPackage setSplitClassLoaderName(int splitIndex, String classLoaderName);
- ParsingPackage setStaticSharedLibVersion(long staticSharedLibVersion);
+ ParsingPackage setStaticSharedLibraryVersion(long staticSharedLibraryVersion);
ParsingPackage setSupportsLargeScreens(int supportsLargeScreens);
@@ -354,7 +354,7 @@
ParsingPackage setTargetSandboxVersion(int targetSandboxVersion);
- ParsingPackage setTheme(int theme);
+ ParsingPackage setThemeRes(int theme);
ParsingPackage setRequestForegroundServiceExemption(boolean requestForegroundServiceExemption);
@@ -512,7 +512,7 @@
boolean isAnyDensity();
- boolean isBaseHardwareAccelerated();
+ boolean isHardwareAccelerated();
boolean isCantSaveState();
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index c6e1793..2a2640d 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1044,9 +1044,9 @@
}
return input.success(pkg
- .setLeavingSharedUid(leaving)
+ .setLeavingSharedUser(leaving)
.setSharedUserId(str.intern())
- .setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
+ .setSharedUserLabelRes(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
}
private static ParseResult<ParsingPackage> parseKeySets(ParseInput input,
@@ -1869,7 +1869,7 @@
return input.error("Empty class name in package " + packageName);
}
- pkg.setClassName(outInfoName);
+ pkg.setApplicationClassName(outInfoName);
}
TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
@@ -1939,7 +1939,7 @@
fullBackupContent = v.data == 0 ? -1 : 0;
}
- pkg.setFullBackupContent(fullBackupContent);
+ pkg.setFullBackupContentRes(fullBackupContent);
}
if (DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
@@ -2247,7 +2247,7 @@
.setOnBackInvokedCallbackEnabled(bool(false, R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
- .setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
+ .setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
.setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
.setUsesCleartextTraffic(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
// Ints Default 0
@@ -2258,14 +2258,14 @@
.setMaxAspectRatio(aFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, sa))
.setMinAspectRatio(aFloat(R.styleable.AndroidManifestApplication_minAspectRatio, sa))
// Resource ID
- .setBanner(resId(R.styleable.AndroidManifestApplication_banner, sa))
+ .setBannerRes(resId(R.styleable.AndroidManifestApplication_banner, sa))
.setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
.setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))
- .setLogo(resId(R.styleable.AndroidManifestApplication_logo, sa))
+ .setLogoRes(resId(R.styleable.AndroidManifestApplication_logo, sa))
.setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
.setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
- .setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
- .setDataExtractionRules(
+ .setThemeRes(resId(R.styleable.AndroidManifestApplication_theme, sa))
+ .setDataExtractionRulesRes(
resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
.setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa))
// Strings
@@ -2399,7 +2399,7 @@
}
return input.success(pkg.setStaticSharedLibraryName(lname.intern())
- .setStaticSharedLibVersion(
+ .setStaticSharedLibraryVersion(
PackageInfo.composeLongVersionCode(versionMajor, version))
.setStaticSharedLibrary(true));
} finally {
@@ -2694,7 +2694,7 @@
// Build custom App Details activity info instead of parsing it from xml
return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
- pkg.isBaseHardwareAccelerated()));
+ pkg.isHardwareAccelerated()));
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index f683d0a..a16e659 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -100,7 +100,6 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.pm.KnownPackages;
@@ -191,6 +190,15 @@
}
@Override
+ public void activityRefreshed(IBinder token) {
+ final long origId = Binder.clearCallingIdentity();
+ synchronized (mGlobalLock) {
+ ActivityRecord.activityRefreshedLocked(token);
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ @Override
public void activityTopResumedStateLost() {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
@@ -482,46 +490,13 @@
finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
|| (finishWithRootActivity && r == rootR)) {
- ActivityRecord topActivity =
- r.getTask().getTopNonFinishingActivity();
- boolean passesAsmChecks = topActivity != null
- && topActivity.getUid() == r.getUid();
- if (!passesAsmChecks) {
- Slog.i(TAG, "Finishing task from background. r: " + r);
- FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
- /* caller_uid */
- r.getUid(),
- /* caller_activity_class_name */
- r.info.name,
- /* target_task_top_activity_uid */
- topActivity == null ? -1 : topActivity.getUid(),
- /* target_task_top_activity_class_name */
- topActivity == null ? null : topActivity.info.name,
- /* target_task_is_different */
- false,
- /* target_activity_uid */
- -1,
- /* target_activity_class_name */
- null,
- /* target_intent_action */
- null,
- /* target_intent_flags */
- 0,
- /* action */
- FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
- /* version */
- 1,
- /* multi_window */
- false
- );
- }
// If requested, remove the task that is associated to this activity only if it
// was the root activity in the task. The result code and data is ignored
// because we don't support returning them across task boundaries. Also, to
// keep backwards compatibility we remove the task from recents when finishing
// task with root activity.
mTaskSupervisor.removeTask(tr, false /*killProcess*/,
- finishWithRootActivity, "finish-activity");
+ finishWithRootActivity, "finish-activity", r.getUid(), r.info.name);
res = true;
// Explicitly dismissing the activity so reset its relaunch flag.
r.mRelaunchReason = RELAUNCH_REASON_NONE;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index aca5b61..bf4e25c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -736,7 +736,6 @@
*/
private boolean mWillCloseOrEnterPip;
- @VisibleForTesting
final LetterboxUiController mLetterboxUiController;
/**
@@ -6076,6 +6075,19 @@
r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
}
+ static void activityRefreshedLocked(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ ProtoLog.i(WM_DEBUG_STATES, "Refreshed activity: %s", r);
+ if (r == null) {
+ // In case the record on server side has been removed (e.g. destroy timeout)
+ // and the token could be null.
+ return;
+ }
+ if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
+ r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+ }
+ }
+
static void splashScreenAttachedLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
@@ -9151,6 +9163,8 @@
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
+ notifyDisplayCompatPolicyAboutConfigurationChange(
+ mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
return true;
}
@@ -9219,11 +9233,24 @@
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
+ notifyDisplayCompatPolicyAboutConfigurationChange(
+ mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
+
stopFreezingScreenLocked(false);
return true;
}
+ private void notifyDisplayCompatPolicyAboutConfigurationChange(
+ Configuration newConfig, Configuration lastReportedConfig) {
+ if (mDisplayContent.mDisplayRotationCompatPolicy == null
+ || !shouldBeResumed(/* activeActivity */ null)) {
+ return;
+ }
+ mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+ this, newConfig, lastReportedConfig);
+ }
+
/** Get process configuration, or global config if the process is not set. */
private Configuration getProcessGlobalConfiguration() {
return app != null ? app.getConfiguration() : mAtmService.getGlobalConfiguration();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index c63bd52..ef126a9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -29,6 +29,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.LocaleList;
@@ -622,10 +623,19 @@
@Nullable
public final LocaleList mLocales;
+ /**
+ * Gender for the application, null if app-specific grammatical gender is not set.
+ */
+ @Nullable
+ public final @Configuration.GrammaticalGender
+ Integer mGrammaticalGender;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public PackageConfig(Integer nightMode, LocaleList locales) {
+ public PackageConfig(Integer nightMode, LocaleList locales,
+ @Configuration.GrammaticalGender Integer grammaticalGender) {
mNightMode = nightMode;
mLocales = locales;
+ mGrammaticalGender = grammaticalGender;
}
/**
@@ -660,6 +670,13 @@
PackageConfigurationUpdater setLocales(LocaleList locales);
/**
+ * Sets the gender for the current application. This setting is persisted and will
+ * override the system configuration for this application.
+ */
+ PackageConfigurationUpdater setGrammaticalGender(
+ @Configuration.GrammaticalGender int gender);
+
+ /**
* Commit changes.
* @return true if the configuration changes were persisted,
* false if there were no changes, or if erroneous inputs were provided, such as:
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 42da2a5..3f885f3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -46,6 +46,7 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -98,6 +99,7 @@
import android.app.servertransaction.LaunchActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -140,6 +142,7 @@
import com.android.internal.content.ReferrerIntent;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
@@ -252,6 +255,7 @@
private WindowManagerService mWindowManager;
private AppOpsManager mAppOpsManager;
+ private VirtualDeviceManager mVirtualDeviceManager;
/** Common synchronization logic used to save things to disks. */
PersisterQueue mPersisterQueue;
@@ -895,12 +899,14 @@
final boolean isTransitionForward = r.isTransitionForward();
final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
+
+ final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
- mergedConfiguration.getOverrideConfiguration(),
+ mergedConfiguration.getOverrideConfiguration(), deviceId,
r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
results, newIntents, r.takeOptions(), isTransitionForward,
@@ -1216,6 +1222,17 @@
}
}
+ int getDeviceIdForDisplayId(int displayId) {
+ if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
+ return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+ }
+ if (mVirtualDeviceManager == null) {
+ mVirtualDeviceManager =
+ mService.mContext.getSystemService(VirtualDeviceManager.class);
+ }
+ return mVirtualDeviceManager.getDeviceIdForDisplayId(displayId);
+ }
+
private AppOpsManager getAppOpsManager() {
if (mAppOpsManager == null) {
mAppOpsManager = mService.mContext.getSystemService(AppOpsManager.class);
@@ -1590,11 +1607,11 @@
* @return Returns true if the given task was found and removed.
*/
boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
- String reason) {
+ String reason, int callingUid) {
final Task task =
mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
if (task != null) {
- removeTask(task, killProcess, removeFromRecents, reason);
+ removeTask(task, killProcess, removeFromRecents, reason, callingUid, null);
return true;
}
Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
@@ -1602,10 +1619,52 @@
}
void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
+ removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, null);
+ }
+
+ void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason,
+ int callingUid, String callerActivityClassName) {
if (task.mInRemoveTask) {
// Prevent recursion.
return;
}
+ // We may have already checked that the callingUid has additional clearTask privileges, and
+ // cleared the calling identify. If so, we infer we do not need further restrictions here.
+ // TODO(b/263368846) Move to live with the rest of the ASM logic.
+ if (callingUid != SYSTEM_UID) {
+ ActivityRecord topActivity = task.getTopNonFinishingActivity();
+ boolean passesAsmChecks = topActivity != null
+ && topActivity.getUid() == callingUid;
+ if (!passesAsmChecks) {
+ Slog.i(TAG, "Finishing task from background. t: " + task);
+ FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+ /* caller_uid */
+ callingUid,
+ /* caller_activity_class_name */
+ callerActivityClassName,
+ /* target_task_top_activity_uid */
+ topActivity == null ? -1 : topActivity.getUid(),
+ /* target_task_top_activity_class_name */
+ topActivity == null ? null : topActivity.info.name,
+ /* target_task_is_different */
+ false,
+ /* target_activity_uid */
+ -1,
+ /* target_activity_class_name */
+ null,
+ /* target_intent_action */
+ null,
+ /* target_intent_flags */
+ 0,
+ /* action */
+ FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+ /* version */
+ 1,
+ /* multi_window */
+ false
+ );
+ }
+ }
task.mTransitionController.requestCloseTransitionIfNeeded(task);
task.mInRemoveTask = true;
try {
@@ -1728,7 +1787,7 @@
// Task was trimmed from the recent tasks list -- remove the active task record as well
// since the user won't really be able to go back to it
removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */,
- "recent-task-trimmed");
+ "recent-task-trimmed", SYSTEM_UID);
}
task.removedFromRecents();
}
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index b160af6a..7bd8c53 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -72,15 +72,16 @@
checkCallerOrSystemOrRoot();
synchronized (mService.mGlobalLock) {
- final long origId = Binder.clearCallingIdentity();
+ int origCallingUid = Binder.getCallingUid();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
// We remove the task from recents to preserve backwards
if (!mService.mTaskSupervisor.removeTaskById(mTaskId, false,
- REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
+ REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid)) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
} finally {
- Binder.restoreCallingIdentity(origId);
+ Binder.restoreCallingIdentity(callingIdentity);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 0c6cea8..58d4e82 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -536,16 +536,19 @@
* Applies app-specific nightMode and {@link LocaleList} on requested configuration.
* @return true if any of the requested configuration has been updated.
*/
- public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) {
+ public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales,
+ @Configuration.GrammaticalGender Integer gender) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
nightMode);
boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
locales);
- if (newNightModeSet || newLocalesSet) {
+ boolean newGenderSet = (gender != null) && setOverrideGender(mRequestsTmpConfig,
+ gender);
+ if (newNightModeSet || newLocalesSet || newGenderSet) {
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
- return newNightModeSet || newLocalesSet;
+ return newNightModeSet || newLocalesSet || newGenderSet;
}
/**
@@ -578,6 +581,21 @@
return true;
}
+ /**
+ * Overrides the gender to this ConfigurationContainer.
+ *
+ * @return true if the grammatical gender has been changed.
+ */
+ private boolean setOverrideGender(Configuration requestsTmpConfig,
+ @Configuration.GrammaticalGender int gender) {
+ if (mRequestedOverrideConfiguration.getGrammaticalGender() == gender) {
+ return false;
+ } else {
+ requestsTmpConfig.setGrammaticalGender(gender);
+ return true;
+ }
+ }
+
public boolean isActivityTypeDream() {
return getActivityType() == ACTIVITY_TYPE_DREAM;
}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 8d5d0d5..af135b7 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -211,6 +211,7 @@
* Stops recording on this DisplayContent, and updates the session details.
*/
void stopRecording() {
+ unregisterListener();
if (mRecordedSurface != null) {
// Do not wait for the mirrored surface to be garbage collected, but clean up
// immediately.
@@ -227,7 +228,7 @@
* Ensure recording does not fall back to the display stack; ensure the recording is stopped
* and the client notified by tearing down the virtual display.
*/
- void stopMediaProjection() {
+ private void stopMediaProjection() {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
if (mMediaProjectionManager != null) {
@@ -247,6 +248,16 @@
null, mDisplayContent.mWmService);
}
+ private void unregisterListener() {
+ Task recordedTask = mRecordedWindowContainer != null
+ ? mRecordedWindowContainer.asTask() : null;
+ if (recordedTask == null || !isRecordingContentTask()) {
+ return;
+ }
+ recordedTask.unregisterWindowContainerListener(this);
+ mRecordedWindowContainer = null;
+ }
+
/**
* Start recording to this DisplayContent if it does not have its own content. Captures the
* content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls
@@ -301,6 +312,13 @@
// Retrieve the size of the DisplayArea to mirror.
updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
+ // Notify the client about the visibility of the mirrored region, now that we have begun
+ // capture.
+ if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+ mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ mRecordedWindowContainer.asTask().isVisibleRequested());
+ }
+
// No need to clean up. In SurfaceFlinger, parents hold references to their children. The
// mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
// holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
@@ -389,6 +407,7 @@
*/
private void handleStartRecordingFailed() {
final boolean shouldExitTaskRecording = isRecordingContentTask();
+ unregisterListener();
clearContentRecordingSession();
if (shouldExitTaskRecording) {
// Clean up the cached session first to ensure recording doesn't re-start, since
@@ -478,12 +497,7 @@
"Recorded task is removed, so stop recording on display %d",
mDisplayContent.getDisplayId());
- Task recordedTask = mRecordedWindowContainer != null
- ? mRecordedWindowContainer.asTask() : null;
- if (recordedTask == null || !isRecordingContentTask()) {
- return;
- }
- recordedTask.unregisterWindowContainerListener(this);
+ unregisterListener();
// Stop mirroring and teardown.
clearContentRecordingSession();
// Clean up the cached session first to ensure recording doesn't re-start, since
@@ -501,9 +515,20 @@
mLastOrientation = mergedOverrideConfiguration.orientation;
}
+ // WindowContainerListener
+ @Override
+ public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+ // Check still recording just to be safe.
+ if (isCurrentlyRecording() && mLastRecordedBounds != null) {
+ mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ isVisibleRequested);
+ }
+ }
+
@VisibleForTesting interface MediaProjectionManagerWrapper {
void stopActiveProjection();
void notifyActiveProjectionCapturedContentResized(int width, int height);
+ void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
}
private static final class RemoteMediaProjectionManagerWrapper implements
@@ -543,6 +568,23 @@
}
}
+ @Override
+ public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+ fetchMediaProjectionManager();
+ if (mIMediaProjectionManager == null) {
+ return;
+ }
+ try {
+ mIMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+ isVisible);
+ } catch (RemoteException e) {
+ ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+ "Unable to tell MediaProjectionManagerService about visibility change on "
+ + "the active projection: %s",
+ e);
+ }
+ }
+
private void fetchMediaProjectionManager() {
if (mIMediaProjectionManager != null) {
return;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 044357b..82237bb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -449,6 +449,7 @@
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private final DisplayPolicy mDisplayPolicy;
private final DisplayRotation mDisplayRotation;
+ @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
DisplayFrames mDisplayFrames;
private boolean mInTouchMode;
@@ -1178,6 +1179,13 @@
onDisplayChanged(this);
updateDisplayAreaOrganizers();
+ mDisplayRotationCompatPolicy =
+ // Not checking DeviceConfig value here to allow enabling via DeviceConfig
+ // without the need to restart the device.
+ mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ false)
+ ? new DisplayRotationCompatPolicy(this) : null;
+
mInputMonitor = new InputMonitor(mWmService, this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
@@ -2756,6 +2764,14 @@
}
}
+ if (mDisplayRotationCompatPolicy != null) {
+ int compatOrientation = mDisplayRotationCompatPolicy.getOrientation();
+ if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ mLastOrientationSource = null;
+ return compatOrientation;
+ }
+ }
+
final int orientation = super.getOrientation();
if (!handlesOrientationChangeFromDescendant(orientation)) {
@@ -3318,6 +3334,10 @@
// on the next traversal if it's removed from RootWindowContainer child list.
getPendingTransaction().apply();
mWmService.mWindowPlacerLocked.requestTraversal();
+
+ if (mDisplayRotationCompatPolicy != null) {
+ mDisplayRotationCompatPolicy.dispose();
+ }
}
/** Returns true if a removal action is still being deferred. */
@@ -6505,15 +6525,6 @@
}
/**
- * The MediaProjection instance is torn down.
- */
- @VisibleForTesting void stopMediaProjection() {
- if (mContentRecorder != null) {
- mContentRecorder.stopMediaProjection();
- }
- }
-
- /**
* Sets the incoming recording session. Should only be used when starting to record on
* this display; stopping recording is handled separately when the display is destroyed.
*
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 34bdb7a..cf3a688 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1805,6 +1805,7 @@
final int mHalfFoldSavedRotation;
final boolean mInHalfFoldTransition;
final DeviceStateController.FoldState mFoldState;
+ @Nullable final String mDisplayRotationCompatPolicySummary;
Record(DisplayRotation dr, int fromRotation, int toRotation) {
mFromRotation = fromRotation;
@@ -1839,6 +1840,10 @@
mInHalfFoldTransition = false;
mFoldState = DeviceStateController.FoldState.UNKNOWN;
}
+ mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
+ ? null
+ : dc.mDisplayRotationCompatPolicy
+ .getSummaryForDisplayRotationHistoryRecord();
}
void dump(String prefix, PrintWriter pw) {
@@ -1861,6 +1866,9 @@
+ " mInHalfFoldTransition=" + mInHalfFoldTransition
+ " mFoldState=" + mFoldState);
}
+ if (mDisplayRotationCompatPolicySummary != null) {
+ pw.println(prefix + mDisplayRotationCompatPolicySummary);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
new file mode 100644
index 0000000..18c5c3b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.screenOrientationToString;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Controls camera compatibility treatment that handles orientation mismatch between camera
+ * buffers and an app window for a particular display that can lead to camera issues like sideways
+ * or stretched viewfinder.
+ *
+ * <p>This includes force rotation of fixed orientation activities connected to the camera.
+ *
+ * <p>The treatment is enabled for internal displays that have {@code ignoreOrientationRequest}
+ * display setting enabled and when {@code
+ * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+ */
+ // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
+final class DisplayRotationCompatPolicy {
+
+ // Delay for updating display rotation after Camera connection is closed. Needed to avoid
+ // rotation flickering when an app is flipping between front and rear cameras or when size
+ // compat mode is restarted.
+ // TODO(b/263114289): Consider associating this delay with a specific activity so that if
+ // the new non-camera activity started on top of the camer one we can rotate faster.
+ private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
+ // Delay for updating display rotation after Camera connection is opened. This delay is
+ // selected to be long enough to avoid conflicts with transitions on the app's side.
+ // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
+ // is flipping between front and rear cameras (in case requested orientation changes at
+ // runtime at the same time) or when size compat mode is restarted.
+ private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
+ CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
+ // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+ // client process may not always report the event back to the server, such as process is
+ // crashed or got killed.
+ private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
+
+ private final DisplayContent mDisplayContent;
+ private final WindowManagerService mWmService;
+ private final CameraManager mCameraManager;
+ private final Handler mHandler;
+
+ // Bi-directional map between package names and active camera IDs since we need to 1) get a
+ // camera id by a package name when determining rotation; 2) get a package name by a camera id
+ // when camera connection is closed and we need to clean up our records.
+ @GuardedBy("this")
+ private final CameraIdPackageNameBiMap mCameraIdPackageBiMap = new CameraIdPackageNameBiMap();
+ @GuardedBy("this")
+ private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
+ @GuardedBy("this")
+ private final Set<String> mScheduledOrientationUpdateCameraIdSet = new ArraySet<>();
+
+ private final CameraManager.AvailabilityCallback mAvailabilityCallback =
+ new CameraManager.AvailabilityCallback() {
+ @Override
+ public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
+ notifyCameraOpened(cameraId, packageId);
+ }
+
+ @Override
+ public void onCameraClosed(@NonNull String cameraId) {
+ notifyCameraClosed(cameraId);
+ }
+ };
+
+ @ScreenOrientation
+ private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
+
+ DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent) {
+ this(displayContent, displayContent.mWmService.mH);
+ }
+
+ @VisibleForTesting
+ DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler) {
+ // This constructor is called from DisplayContent constructor. Don't use any fields in
+ // DisplayContent here since they aren't guaranteed to be set.
+ mHandler = handler;
+ mDisplayContent = displayContent;
+ mWmService = displayContent.mWmService;
+ mCameraManager = mWmService.mContext.getSystemService(CameraManager.class);
+ mCameraManager.registerAvailabilityCallback(
+ mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+ }
+
+ void dispose() {
+ mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
+ }
+
+ /**
+ * Determines orientation for Camera compatibility.
+ *
+ * <p>The goal of this function is to compute a orientation which would align orientations of
+ * portrait app window and natural orientation of the device and set opposite to natural
+ * orientation for a landscape app window. This is one of the strongest assumptions that apps
+ * make when they implement camera previews. Since app and natural display orientations aren't
+ * guaranteed to match, the rotation can cause letterboxing.
+ *
+ * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
+ * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
+ */
+ @ScreenOrientation
+ int getOrientation() {
+ mLastReportedOrientation = getOrientationInternal();
+ return mLastReportedOrientation;
+ }
+
+ @ScreenOrientation
+ private synchronized int getOrientationInternal() {
+ if (!isTreatmentEnabledForDisplay()) {
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (!isTreatmentEnabledForActivity(topActivity)) {
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+ boolean isPortraitActivity =
+ topActivity.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+ boolean isNaturalDisplayOrientationPortrait =
+ mDisplayContent.getNaturalOrientation() == ORIENTATION_PORTRAIT;
+ // Rotate portrait-only activity in the natural orientation of the displays (and in the
+ // opposite to natural orientation for landscape-only) since many apps assume that those
+ // are aligned when they compute orientation of the preview.
+ // This means that even for a landscape-only activity and a device with landscape natural
+ // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
+ // natural orientation = portrait window = portait camera is the main wrong assumption
+ // that apps make when they implement camera previews so landscape windows need be
+ // rotated in the orientation oposite to the natural one even if it's portrait.
+ // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
+ // of the portrait and landscape orientation requests.
+ int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
+ || (!isPortraitActivity && !isNaturalDisplayOrientationPortrait)
+ ? SCREEN_ORIENTATION_PORTRAIT
+ : SCREEN_ORIENTATION_LANDSCAPE;
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is ignoring all orientation requests, camera is active "
+ + "and the top activity is eligible for force rotation, return %s,"
+ + "portrait activity: %b, is natural orientation portrait: %b.",
+ mDisplayContent.mDisplayId, screenOrientationToString(orientation),
+ isPortraitActivity, isNaturalDisplayOrientationPortrait);
+ return orientation;
+ }
+
+ /**
+ * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ */
+ void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
+ Configuration lastReportedConfig) {
+ if (!isTreatmentEnabledForDisplay()
+ || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+ || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+ return;
+ }
+ boolean cycleThroughStop = mWmService.mLetterboxConfiguration
+ .isCameraCompatRefreshCycleThroughStopEnabled();
+ try {
+ activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
+ ProtoLog.v(WM_DEBUG_STATES,
+ "Refershing activity for camera compatibility treatment, "
+ + "activityRecord=%s", activity);
+ final ClientTransaction transaction = ClientTransaction.obtain(
+ activity.app.getThread(), activity.token);
+ transaction.addCallback(
+ RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+ transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+ activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+ mHandler.postDelayed(
+ () -> onActivityRefreshed(activity),
+ REFRESH_CALLBACK_TIMEOUT_MS);
+ } catch (RemoteException e) {
+ activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+ }
+ }
+
+ void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+ }
+
+ String getSummaryForDisplayRotationHistoryRecord() {
+ String summaryIfEnabled = "";
+ if (isTreatmentEnabledForDisplay()) {
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ summaryIfEnabled =
+ " mLastReportedOrientation="
+ + screenOrientationToString(mLastReportedOrientation)
+ + " topActivity="
+ + (topActivity == null ? "null" : topActivity.shortComponentName)
+ + " isTreatmentEnabledForActivity="
+ + isTreatmentEnabledForActivity(topActivity)
+ + " CameraIdPackageNameBiMap="
+ + mCameraIdPackageBiMap.getSummaryForDisplayRotationHistoryRecord();
+ }
+ return "DisplayRotationCompatPolicy{"
+ + " isTreatmentEnabledForDisplay=" + isTreatmentEnabledForDisplay()
+ + summaryIfEnabled
+ + " }";
+ }
+
+ // Refreshing only when configuration changes after rotation.
+ private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
+ Configuration lastReportedConfig) {
+ return newConfig.windowConfiguration.getDisplayRotation()
+ != lastReportedConfig.windowConfiguration.getDisplayRotation()
+ && isTreatmentEnabledForActivity(activity);
+ }
+
+ /**
+ * Whether camera compat treatment is enabled for the display.
+ *
+ * <p>Conditions that need to be met:
+ * <ul>
+ * <li>{@code R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+ * <li>Setting {@code ignoreOrientationRequest} is enabled for the display.
+ * <li>Associated {@link DisplayContent} is for internal display. See b/225928882
+ * that tracks supporting external displays in the future.
+ * </ul>
+ */
+ private boolean isTreatmentEnabledForDisplay() {
+ return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true)
+ && mDisplayContent.getIgnoreOrientationRequest()
+ // TODO(b/225928882): Support camera compat rotation for external displays
+ && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
+ }
+
+ /**
+ * Whether camera compat treatment is applicable for the given activity.
+ *
+ * <p>Conditions that need to be met:
+ * <ul>
+ * <li>{@link #isCameraActiveForPackage} is {@code true} for the activity.
+ * <li>The activity is in fullscreen
+ * <li>The activity has fixed orientation but not "locked" or "nosensor" one.
+ * </ul>
+ */
+ private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
+ return activity != null && !activity.inMultiWindowMode()
+ && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
+ // "locked" and "nosensor" values are often used by camera apps that can't
+ // handle dynamic changes so we shouldn't force rotate them.
+ && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
+ && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
+ && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
+ }
+
+ private synchronized void notifyCameraOpened(
+ @NonNull String cameraId, @NonNull String packageName) {
+ // If an activity is restarting or camera is flipping, the camera connection can be
+ // quickly closed and reopened.
+ mScheduledToBeRemovedCameraIdSet.remove(cameraId);
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is open for package %s",
+ mDisplayContent.mDisplayId, cameraId, packageName);
+ // Some apps can’t handle configuration changes coming at the same time with Camera setup
+ // so delaying orientation update to accomadate for that.
+ mScheduledOrientationUpdateCameraIdSet.add(cameraId);
+ mHandler.postDelayed(
+ () -> delayedUpdateOrientationWithWmLock(cameraId, packageName),
+ CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS);
+ }
+
+ private void updateOrientationWithWmLock() {
+ synchronized (mWmService.mGlobalLock) {
+ mDisplayContent.updateOrientation();
+ }
+ }
+
+ private void delayedUpdateOrientationWithWmLock(
+ @NonNull String cameraId, @NonNull String packageName) {
+ synchronized (this) {
+ if (!mScheduledOrientationUpdateCameraIdSet.remove(cameraId)) {
+ // Orientation update has happened already or was cancelled because
+ // camera was closed.
+ return;
+ }
+ mCameraIdPackageBiMap.put(packageName, cameraId);
+ }
+ updateOrientationWithWmLock();
+ }
+
+ private synchronized void notifyCameraClosed(@NonNull String cameraId) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+ mDisplayContent.mDisplayId, cameraId);
+ mScheduledToBeRemovedCameraIdSet.add(cameraId);
+ // No need to update orientation for this camera if it's already closed.
+ mScheduledOrientationUpdateCameraIdSet.remove(cameraId);
+ scheduleRemoveCameraId(cameraId);
+ }
+
+ // Delay is needed to avoid rotation flickering when an app is flipping between front and
+ // rear cameras, when size compat mode is restarted or activity is being refreshed.
+ private void scheduleRemoveCameraId(@NonNull String cameraId) {
+ mHandler.postDelayed(
+ () -> removeCameraId(cameraId),
+ CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS);
+ }
+
+ private void removeCameraId(String cameraId) {
+ synchronized (this) {
+ if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) {
+ // Already reconnected to this camera, no need to clean up.
+ return;
+ }
+ if (isActivityForCameraIdRefreshing(cameraId)) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is closed but activity is"
+ + " still refreshing. Rescheduling an update.",
+ mDisplayContent.mDisplayId, cameraId);
+ mScheduledToBeRemovedCameraIdSet.add(cameraId);
+ scheduleRemoveCameraId(cameraId);
+ return;
+ }
+ mCameraIdPackageBiMap.removeCameraId(cameraId);
+ }
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is closed, updating rotation.",
+ mDisplayContent.mDisplayId, cameraId);
+ updateOrientationWithWmLock();
+ }
+
+ private boolean isActivityForCameraIdRefreshing(String cameraId) {
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (!isTreatmentEnabledForActivity(topActivity)) {
+ return false;
+ }
+ String activeCameraId = mCameraIdPackageBiMap.getCameraId(topActivity.packageName);
+ if (activeCameraId == null || activeCameraId != cameraId) {
+ return false;
+ }
+ return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+ }
+
+ private static class CameraIdPackageNameBiMap {
+
+ private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
+ private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
+
+ void put(String packageName, String cameraId) {
+ // Always using the last connected camera ID for the package even for the concurrent
+ // camera use case since we can't guess which camera is more important anyway.
+ removePackageName(packageName);
+ removeCameraId(cameraId);
+ mPackageToCameraIdMap.put(packageName, cameraId);
+ mCameraIdToPackageMap.put(cameraId, packageName);
+ }
+
+ boolean containsPackageName(String packageName) {
+ return mPackageToCameraIdMap.containsKey(packageName);
+ }
+
+ @Nullable
+ String getCameraId(String packageName) {
+ return mPackageToCameraIdMap.get(packageName);
+ }
+
+ void removeCameraId(String cameraId) {
+ String packageName = mCameraIdToPackageMap.get(cameraId);
+ if (packageName == null) {
+ return;
+ }
+ mPackageToCameraIdMap.remove(packageName, cameraId);
+ mCameraIdToPackageMap.remove(cameraId, packageName);
+ }
+
+ String getSummaryForDisplayRotationHistoryRecord() {
+ return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }";
+ }
+
+ private void removePackageName(String packageName) {
+ String cameraId = mPackageToCameraIdMap.get(packageName);
+ if (cameraId == null) {
+ return;
+ }
+ mPackageToCameraIdMap.remove(packageName, cameraId);
+ mCameraIdToPackageMap.remove(cameraId, packageName);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 3eca364..a7bf595f 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -191,6 +191,20 @@
// Allows to enable letterboxing strategy for translucent activities ignoring flags.
private boolean mTranslucentLetterboxingOverrideEnabled;
+ // Whether camera compatibility treatment is enabled.
+ // See DisplayRotationCompatPolicy for context.
+ private final boolean mIsCameraCompatTreatmentEnabled;
+
+ // Whether activity "refresh" in camera compatibility treatment is enabled.
+ // See RefreshCallbackItem for context.
+ private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
+
+ // Whether activity "refresh" in camera compatibility treatment should happen using the
+ // "stopped -> resumed" cycle rather than "paused -> resumed" cycle. Using "stop -> resumed"
+ // cycle by default due to higher success rate confirmed with app compatibility testing.
+ // See RefreshCallbackItem for context.
+ private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+
LetterboxConfiguration(Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
() -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
@@ -241,6 +255,8 @@
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEnabledForTranslucentActivities);
+ mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
+ R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
}
@@ -947,9 +963,65 @@
isDeviceInTabletopMode, nextVerticalPosition);
}
- // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
+ // TODO(b/262378106): Cache a runtime flag and implement
+ // DeviceConfig.OnPropertiesChangedListener
static boolean isTranslucentLetterboxingAllowed() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
"enable_translucent_activity_letterbox", false);
}
+
+ /** Whether camera compatibility treatment is enabled. */
+ boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
+ return mIsCameraCompatTreatmentEnabled
+ && (!checkDeviceConfig || isCameraCompatTreatmentAllowed());
+ }
+
+ // TODO(b/262977416): Cache a runtime flag and implement
+ // DeviceConfig.OnPropertiesChangedListener
+ private static boolean isCameraCompatTreatmentAllowed() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ "enable_camera_compat_treatment", false);
+ }
+
+ /** Whether camera compatibility refresh is enabled. */
+ boolean isCameraCompatRefreshEnabled() {
+ return mIsCameraCompatTreatmentRefreshEnabled;
+ }
+
+ /** Overrides whether camera compatibility treatment is enabled. */
+ void setCameraCompatRefreshEnabled(boolean enabled) {
+ mIsCameraCompatTreatmentRefreshEnabled = enabled;
+ }
+
+ /**
+ * Resets whether camera compatibility treatment is enabled to {@code true}.
+ */
+ void resetCameraCompatRefreshEnabled() {
+ mIsCameraCompatTreatmentRefreshEnabled = true;
+ }
+
+ /**
+ * Whether activity "refresh" in camera compatibility treatment should happen using the
+ * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+ */
+ boolean isCameraCompatRefreshCycleThroughStopEnabled() {
+ return mIsCameraCompatRefreshCycleThroughStopEnabled;
+ }
+
+ /**
+ * Overrides whether activity "refresh" in camera compatibility treatment should happen using
+ * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+ */
+ void setCameraCompatRefreshCycleThroughStopEnabled(boolean enabled) {
+ mIsCameraCompatRefreshCycleThroughStopEnabled = enabled;
+ }
+
+ /**
+ * Resets whether activity "refresh" in camera compatibility treatment should happen using
+ * "stopped -> resumed" cycle rather than "paused -> resumed" cycle to {@code true}.
+ */
+ void resetCameraCompatRefreshCycleThroughStopEnabled() {
+ mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9cb94c6..fd7e082 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -80,6 +80,7 @@
// SizeCompatTests and LetterboxTests but not all.
// TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the
// hierarchy in addition to ActivityRecord (Task, DisplayArea, ...).
+// TODO(b/263021211): Consider renaming to more generic CompatUIController.
final class LetterboxUiController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
@@ -125,6 +126,11 @@
@Nullable
private Letterbox mLetterbox;
+ // Whether activity "refresh" was requested but not finished in
+ // ActivityRecord#activityResumedLocked following the camera compat force rotation in
+ // DisplayRotationCompatPolicy.
+ private boolean mIsRefreshAfterRotationRequested;
+
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mLetterboxConfiguration = wmService.mLetterboxConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
@@ -147,6 +153,18 @@
}
}
+ /**
+ * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
+ * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+ */
+ boolean isRefreshAfterRotationRequested() {
+ return mIsRefreshAfterRotationRequested;
+ }
+
+ void setIsRefreshAfterRotationRequested(boolean isRequested) {
+ mIsRefreshAfterRotationRequested = isRequested;
+ }
+
boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 18a7d2e..23127ac 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
import android.annotation.NonNull;
import android.content.res.Configuration;
import android.os.Environment;
@@ -165,7 +167,8 @@
if (modifiedRecord != null) {
container.applyAppSpecificConfig(modifiedRecord.mNightMode,
LocaleOverlayHelper.combineLocalesIfOverlayExists(
- modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()));
+ modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()),
+ modifiedRecord.mGrammaticalGender);
}
}
}
@@ -188,16 +191,19 @@
}
boolean isNightModeChanged = updateNightMode(impl.getNightMode(), record);
boolean isLocalesChanged = updateLocales(impl.getLocales(), record);
+ boolean isGenderChanged = updateGender(impl.getGrammaticalGender(), record);
if ((record.mNightMode == null || record.isResetNightMode())
- && (record.mLocales == null || record.mLocales.isEmpty())) {
+ && (record.mLocales == null || record.mLocales.isEmpty())
+ && (record.mGrammaticalGender == null
+ || record.mGrammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED)) {
// if all values default to system settings, we can remove the package.
removePackage(packageName, userId);
// if there was a pre-existing record for the package that was deleted,
// we return true (since it was successfully deleted), else false (since there was
// no change to the previous state).
return isRecordPresent;
- } else if (!isNightModeChanged && !isLocalesChanged) {
+ } else if (!isNightModeChanged && !isLocalesChanged && !isGenderChanged) {
return false;
} else {
final PackageConfigRecord pendingRecord =
@@ -211,7 +217,8 @@
}
if (!updateNightMode(record.mNightMode, writeRecord)
- && !updateLocales(record.mLocales, writeRecord)) {
+ && !updateLocales(record.mLocales, writeRecord)
+ && !updateGender(record.mGrammaticalGender, writeRecord)) {
return false;
}
@@ -240,6 +247,15 @@
return true;
}
+ private boolean updateGender(@Configuration.GrammaticalGender Integer requestedGender,
+ PackageConfigRecord record) {
+ if (requestedGender == null || requestedGender.equals(record.mGrammaticalGender)) {
+ return false;
+ }
+ record.mGrammaticalGender = requestedGender;
+ return true;
+ }
+
@GuardedBy("mLock")
void removeUser(int userId) {
synchronized (mLock) {
@@ -305,7 +321,9 @@
return null;
}
return new ActivityTaskManagerInternal.PackageConfig(
- packageConfigRecord.mNightMode, packageConfigRecord.mLocales);
+ packageConfigRecord.mNightMode,
+ packageConfigRecord.mLocales,
+ packageConfigRecord.mGrammaticalGender);
}
}
@@ -336,6 +354,8 @@
final int mUserId;
Integer mNightMode;
LocaleList mLocales;
+ @Configuration.GrammaticalGender
+ Integer mGrammaticalGender;
PackageConfigRecord(String name, int userId) {
mName = name;
diff --git a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
index f3be66c..2cf8a4a 100644
--- a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
+++ b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.os.Binder;
import android.os.LocaleList;
import android.util.ArraySet;
@@ -33,6 +34,8 @@
private final Optional<Integer> mPid;
private Integer mNightMode;
private LocaleList mLocales;
+ private @Configuration.GrammaticalGender
+ int mGrammaticalGender;
private String mPackageName;
private int mUserId;
private ActivityTaskManagerService mAtm;
@@ -68,6 +71,15 @@
}
@Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater setGrammaticalGender(
+ @Configuration.GrammaticalGender int gender) {
+ synchronized (this) {
+ mGrammaticalGender = gender;
+ }
+ return this;
+ }
+
+ @Override
public boolean commit() {
synchronized (this) {
synchronized (mAtm.mGlobalLock) {
@@ -112,12 +124,12 @@
for (int i = processes.size() - 1; i >= 0; i--) {
final WindowProcessController wpc = processes.valueAt(i);
if (wpc.mInfo.packageName.equals(packageName)) {
- wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+ wpc.applyAppSpecificConfig(mNightMode, localesOverride, mGrammaticalGender);
}
// Always inform individual activities about the update, since activities from other
// packages may be sharing this process
wpc.updateAppSpecificSettingsForAllActivitiesInPackage(packageName, mNightMode,
- localesOverride);
+ localesOverride, mGrammaticalGender);
}
}
@@ -128,4 +140,9 @@
LocaleList getLocales() {
return mLocales;
}
+
+ @Configuration.GrammaticalGender
+ Integer getGrammaticalGender() {
+ return mGrammaticalGender;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ce41ae7..6737052 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1305,6 +1305,11 @@
if (parent != null) {
parent.onChildVisibleRequestedChanged(this);
}
+
+ // Notify listeners about visibility change.
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onVisibleRequestedChanged(mVisibleRequested);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainerListener.java b/services/core/java/com/android/server/wm/WindowContainerListener.java
index ac1fe17..c1ee254 100644
--- a/services/core/java/com/android/server/wm/WindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/WindowContainerListener.java
@@ -27,4 +27,13 @@
/** Called when {@link WindowContainer#removeImmediately()} is invoked. */
default void onRemoved() {}
+
+ /**
+ * Only invoked if the child successfully requested a visibility change.
+ *
+ * @param isVisibleRequested The current {@link WindowContainer#isVisibleRequested()} of this
+ * {@link WindowContainer} (not of the child).
+ * @see WindowContainer#onChildVisibleRequestedChanged(WindowContainer)
+ */
+ default void onVisibleRequestedChanged(boolean isVisibleRequested) { }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 1934b8c..e2c9c17 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -53,6 +53,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
@@ -821,54 +822,6 @@
return 0;
}
- private int runSetLetterboxIsHorizontalReachabilityEnabled(PrintWriter pw)
- throws RemoteException {
- String arg = getNextArg();
- final boolean enabled;
- switch (arg) {
- case "true":
- case "1":
- enabled = true;
- break;
- case "false":
- case "0":
- enabled = false;
- break;
- default:
- getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
- return -1;
- }
-
- synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(enabled);
- }
- return 0;
- }
-
- private int runSetLetterboxIsVerticalReachabilityEnabled(PrintWriter pw)
- throws RemoteException {
- String arg = getNextArg();
- final boolean enabled;
- switch (arg) {
- case "true":
- case "1":
- enabled = true;
- break;
- case "false":
- case "0":
- enabled = false;
- break;
- default:
- getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
- return -1;
- }
-
- synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setIsVerticalReachabilityEnabled(enabled);
- }
- return 0;
- }
-
private int runSetLetterboxDefaultPositionForHorizontalReachability(PrintWriter pw)
throws RemoteException {
@LetterboxHorizontalReachabilityPosition final int position;
@@ -931,32 +884,13 @@
return 0;
}
- private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
- String arg = getNextArg();
- final boolean enabled;
- switch (arg) {
- case "true":
- case "1":
- enabled = true;
- break;
- case "false":
- case "0":
- enabled = false;
- break;
- default:
- getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
- return -1;
- }
-
- synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setIsEducationEnabled(enabled);
- }
- return 0;
- }
-
- private int runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(PrintWriter pw)
+ private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter)
throws RemoteException {
String arg = getNextArg();
+ if (arg == null) {
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got empty input.");
+ return -1;
+ }
final boolean enabled;
switch (arg) {
case "true":
@@ -973,30 +907,7 @@
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(enabled);
- }
- return 0;
- }
-
- private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
- String arg = getNextArg();
- final boolean enabled;
- switch (arg) {
- case "true":
- case "1":
- enabled = true;
- break;
- case "false":
- case "0":
- enabled = false;
- break;
- default:
- getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
- return -1;
- }
-
- synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
+ setter.accept(enabled);
}
return 0;
}
@@ -1039,10 +950,12 @@
runSetLetterboxVerticalPositionMultiplier(pw);
break;
case "--isHorizontalReachabilityEnabled":
- runSetLetterboxIsHorizontalReachabilityEnabled(pw);
+ runSetBooleanFlag(pw, mLetterboxConfiguration
+ ::setIsHorizontalReachabilityEnabled);
break;
case "--isVerticalReachabilityEnabled":
- runSetLetterboxIsVerticalReachabilityEnabled(pw);
+ runSetBooleanFlag(pw, mLetterboxConfiguration
+ ::setIsVerticalReachabilityEnabled);
break;
case "--defaultPositionForHorizontalReachability":
runSetLetterboxDefaultPositionForHorizontalReachability(pw);
@@ -1051,13 +964,23 @@
runSetLetterboxDefaultPositionForVerticalReachability(pw);
break;
case "--isEducationEnabled":
- runSetLetterboxIsEducationEnabled(pw);
+ runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled);
break;
case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
- runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
+ runSetBooleanFlag(pw, mLetterboxConfiguration
+ ::setIsSplitScreenAspectRatioForUnresizableAppsEnabled);
break;
case "--isTranslucentLetterboxingEnabled":
- runSetTranslucentLetterboxingEnabled(pw);
+ runSetBooleanFlag(pw, mLetterboxConfiguration
+ ::setTranslucentLetterboxingOverrideEnabled);
+ break;
+ case "--isCameraCompatRefreshEnabled":
+ runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+ .setCameraCompatRefreshEnabled(enabled));
+ break;
+ case "--isCameraCompatRefreshCycleThroughStopEnabled":
+ runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+ .setCameraCompatRefreshCycleThroughStopEnabled(enabled));
break;
default:
getErrPrintWriter().println(
@@ -1104,27 +1027,34 @@
mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
break;
case "isHorizontalReachabilityEnabled":
- mLetterboxConfiguration.getIsHorizontalReachabilityEnabled();
+ mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
break;
case "isVerticalReachabilityEnabled":
- mLetterboxConfiguration.getIsVerticalReachabilityEnabled();
+ mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
break;
case "defaultPositionForHorizontalReachability":
- mLetterboxConfiguration.getDefaultPositionForHorizontalReachability();
+ mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
break;
case "defaultPositionForVerticalReachability":
- mLetterboxConfiguration.getDefaultPositionForVerticalReachability();
+ mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
break;
case "isEducationEnabled":
- mLetterboxConfiguration.getIsEducationEnabled();
+ mLetterboxConfiguration.resetIsEducationEnabled();
break;
case "isSplitScreenAspectRatioForUnresizableAppsEnabled":
mLetterboxConfiguration
- .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
break;
case "isTranslucentLetterboxingEnabled":
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
break;
+ case "isCameraCompatRefreshEnabled":
+ mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+ break;
+ case "isCameraCompatRefreshCycleThroughStopEnabled":
+ mLetterboxConfiguration
+ .resetCameraCompatRefreshCycleThroughStopEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1226,6 +1156,8 @@
mLetterboxConfiguration.resetIsEducationEnabled();
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+ mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+ mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
}
}
@@ -1270,6 +1202,12 @@
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
+ mLetterboxConfiguration
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+
+ pw.println(" Is activity \"refresh\" in camera compatibility treatment enabled: "
+ + mLetterboxConfiguration.isCameraCompatRefreshEnabled());
+ pw.println(" Refresh using \"stopped -> resumed\" cycle: "
+ + mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled());
+
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1479,7 +1417,12 @@
pw.println(" unresizable apps.");
pw.println(" --isTranslucentLetterboxingEnabled [true|1|false|0]");
pw.println(" Whether letterboxing for translucent activities is enabled.");
-
+ pw.println(" --isCameraCompatRefreshEnabled [true|1|false|0]");
+ pw.println(" Whether camera compatibility refresh is enabled.");
+ pw.println(" --isCameraCompatRefreshCycleThroughStopEnabled [true|1|false|0]");
+ pw.println(" Whether activity \"refresh\" in camera compatibility treatment should");
+ pw.println(" happen using the \"stopped -> resumed\" cycle rather than");
+ pw.println(" \"paused -> resumed\" cycle.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index dcd30bb..91452c6 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -52,6 +52,7 @@
import android.app.IApplicationThread;
import android.app.ProfilerInfo;
import android.app.servertransaction.ConfigurationChangeItem;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -206,6 +207,7 @@
/** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */
private volatile boolean mHasCachedConfiguration;
+ private int mTopActivityDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
/**
* Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not
* registered.
@@ -868,13 +870,13 @@
// TODO(b/199277729): Consider whether we need to add special casing for edge cases like
// activity-embeddings etc.
void updateAppSpecificSettingsForAllActivitiesInPackage(String packageName, Integer nightMode,
- LocaleList localesOverride) {
+ LocaleList localesOverride, @Configuration.GrammaticalGender int gender) {
for (int i = mActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = mActivities.get(i);
// Activities from other packages could be sharing this process. Only propagate updates
// to those activities that are part of the package whose app-specific settings changed
if (packageName.equals(r.packageName)
- && r.applyAppSpecificConfig(nightMode, localesOverride)
+ && r.applyAppSpecificConfig(nightMode, localesOverride, gender)
&& r.isVisibleRequested()) {
r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
}
@@ -1381,8 +1383,16 @@
@Override
public void onConfigurationChanged(Configuration newGlobalConfig) {
super.onConfigurationChanged(newGlobalConfig);
+
+ // If deviceId for the top-activity changed, schedule passing it to the app process.
+ boolean topActivityDeviceChanged = false;
+ int deviceId = getTopActivityDeviceId();
+ if (deviceId != mTopActivityDeviceId) {
+ topActivityDeviceChanged = true;
+ }
+
final Configuration config = getConfiguration();
- if (mLastReportedConfiguration.equals(config)) {
+ if (mLastReportedConfiguration.equals(config) & !topActivityDeviceChanged) {
// Nothing changed.
if (Build.IS_DEBUGGABLE && mHasImeService) {
// TODO (b/135719017): Temporary log for debugging IME service.
@@ -1396,7 +1406,34 @@
mHasPendingConfigurationChange = true;
return;
}
- dispatchConfiguration(config);
+
+ // TODO(b/263402938): Add tests that capture the deviceId dispatch to the client.
+ mTopActivityDeviceId = deviceId;
+ dispatchConfiguration(config, topActivityDeviceChanged ? mTopActivityDeviceId
+ : VirtualDeviceManager.DEVICE_ID_INVALID);
+ }
+
+ private int getTopActivityDeviceId() {
+ ActivityRecord topActivity = getTopNonFinishingActivity();
+ int updatedDeviceId = mTopActivityDeviceId;
+ if (topActivity != null && topActivity.mDisplayContent != null) {
+ updatedDeviceId = mAtm.mTaskSupervisor.getDeviceIdForDisplayId(
+ topActivity.mDisplayContent.mDisplayId);
+ }
+ return updatedDeviceId;
+ }
+
+ @Nullable
+ private ActivityRecord getTopNonFinishingActivity() {
+ if (mActivities.isEmpty()) {
+ return null;
+ }
+ for (int i = mActivities.size() - 1; i >= 0; i--) {
+ if (!mActivities.get(i).finishing) {
+ return mActivities.get(i);
+ }
+ }
+ return null;
}
@Override
@@ -1423,6 +1460,10 @@
}
void dispatchConfiguration(Configuration config) {
+ dispatchConfiguration(config, getTopActivityDeviceId());
+ }
+
+ void dispatchConfiguration(Configuration config, int deviceId) {
mHasPendingConfigurationChange = false;
if (mThread == null) {
if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1449,10 +1490,16 @@
}
}
- scheduleConfigurationChange(mThread, config);
+ scheduleConfigurationChange(mThread, config, deviceId);
}
private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
+ // By default send invalid deviceId as no-op signal so it's not updated on the client side.
+ scheduleConfigurationChange(thread, config, VirtualDeviceManager.DEVICE_ID_INVALID);
+ }
+
+ private void scheduleConfigurationChange(IApplicationThread thread, Configuration config,
+ int deviceId) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config);
if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1462,7 +1509,7 @@
mHasCachedConfiguration = false;
try {
mAtm.getLifecycleManager().scheduleTransaction(thread,
- ConfigurationChangeItem.obtain(config));
+ ConfigurationChangeItem.obtain(config, deviceId));
} catch (Exception e) {
Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
}
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index d37f3bd..a1c5708 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -58,6 +58,7 @@
jmethodID method_gnssAgcBuilderBuild;
jmethodID method_gnssMeasurementsEventBuilderCtor;
jmethodID method_gnssMeasurementsEventBuilderSetClock;
+jmethodID method_gnssMeasurementsEventBuilderSetFullTracking;
jmethodID method_gnssMeasurementsEventBuilderSetMeasurements;
jmethodID method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls;
jmethodID method_gnssMeasurementsEventBuilderBuild;
@@ -109,6 +110,10 @@
env->GetMethodID(class_gnssMeasurementsEventBuilder, "setGnssAutomaticGainControls",
"([Landroid/location/GnssAutomaticGainControl;)"
"Landroid/location/GnssMeasurementsEvent$Builder;");
+ method_gnssMeasurementsEventBuilderSetFullTracking =
+ env->GetMethodID(class_gnssMeasurementsEventBuilder, "setFullTracking",
+ "(Z)"
+ "Landroid/location/GnssMeasurementsEvent$Builder;");
method_gnssMeasurementsEventBuilderBuild =
env->GetMethodID(class_gnssMeasurementsEventBuilder, "build",
"()Landroid/location/GnssMeasurementsEvent;");
@@ -228,7 +233,8 @@
}
void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
- jobjectArray measurementArray, jobjectArray gnssAgcArray) {
+ jobjectArray measurementArray, jobjectArray gnssAgcArray,
+ bool hasFullTracking, jboolean isFullTracking) {
jobject gnssMeasurementsEventBuilderObject =
env->NewObject(class_gnssMeasurementsEventBuilder,
method_gnssMeasurementsEventBuilderCtor);
@@ -240,6 +246,11 @@
callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
gnssAgcArray);
+ if (hasFullTracking) {
+ callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetFullTracking,
+ isFullTracking);
+ }
jobject gnssMeasurementsEventObject =
env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
method_gnssMeasurementsEventBuilderBuild);
@@ -381,7 +392,14 @@
jobjectArray gnssAgcArray = nullptr;
gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
- setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
+ if (this->getInterfaceVersion() >= 3) {
+ setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
+ /*hasFullTracking=*/true, data.isFullTracking);
+ } else {
+ setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
+ /*hasFullTracking=*/false,
+ /*isFullTracking=*/JNI_FALSE);
+ }
env->DeleteLocalRef(clock);
env->DeleteLocalRef(measurementArray);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index c8f1803..fde56881 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -48,7 +48,8 @@
void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz);
void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
- jobjectArray measurementArray, jobjectArray gnssAgcArray);
+ jobjectArray measurementArray, jobjectArray gnssAgcArray,
+ bool hasFullTracking, jboolean isFullTracking);
class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
public:
@@ -140,7 +141,9 @@
size_t count = getMeasurementCount(data);
jobjectArray measurementArray =
translateAllGnssMeasurements(env, data.measurements.data(), count);
- setMeasurementData(env, mCallbacksObj, clock, measurementArray, nullptr);
+ setMeasurementData(env, mCallbacksObj, clock, measurementArray, /*gnssAgcArray=*/nullptr,
+ /*hasFullTracking=*/false,
+ /*isFullTracking=*/JNI_FALSE);
env->DeleteLocalRef(clock);
env->DeleteLocalRef(measurementArray);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bcb4ec9..6b45ba2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -132,6 +132,7 @@
import com.android.server.display.color.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.grammaticalinflection.GrammaticalInflectionService;
import com.android.server.gpu.GpuService;
import com.android.server.graphics.fonts.FontManagerService;
import com.android.server.hdmi.HdmiControlService;
@@ -425,6 +426,8 @@
"com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
private static final String AD_SERVICES_MANAGER_SERVICE_CLASS =
"com.android.server.adservices.AdServicesManagerService$Lifecycle";
+ private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
+ "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
@@ -1512,6 +1515,8 @@
t.traceBegin("InstallSystemProviders");
mActivityManagerService.getContentProviderHelper().installSystemProviders();
+ // Device configuration used to be part of System providers
+ mSystemServiceManager.startService(UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS);
// Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
SQLiteCompatibilityWalFlags.reset();
t.traceEnd();
@@ -1765,6 +1770,14 @@
}
t.traceEnd();
+ t.traceBegin("StartGrammarInflectionService");
+ try {
+ mSystemServiceManager.startService(GrammaticalInflectionService.class);
+ } catch (Throwable e) {
+ reportWtf("starting GrammarInflectionService service", e);
+ }
+ t.traceEnd();
+
t.traceBegin("UpdatePackagesIfNeeded");
try {
Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index c8e2676..c6b7736 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -753,7 +753,7 @@
.setPVersionCode(pkg.getLongVersionCode())
.setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
.setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
- .setSharedUserId(pkg.getSharedUserLabel())
+ .setSharedUserId(pkg.getSharedUserLabelRes())
.build();
}
@@ -761,9 +761,9 @@
public static void assertPackagesEqual(AndroidPackage a, AndroidPackage b) {
assertEquals(a.getBaseRevisionCode(), b.getBaseRevisionCode());
- assertEquals(a.isBaseHardwareAccelerated(), b.isBaseHardwareAccelerated());
+ assertEquals(a.isHardwareAccelerated(), b.isHardwareAccelerated());
assertEquals(a.getLongVersionCode(), b.getLongVersionCode());
- assertEquals(a.getSharedUserLabel(), b.getSharedUserLabel());
+ assertEquals(a.getSharedUserLabelRes(), b.getSharedUserLabelRes());
assertEquals(a.getInstallLocation(), b.getInstallLocation());
assertEquals(a.isCoreApp(), b.isCoreApp());
assertEquals(a.isRequiredForAllUsers(), b.isRequiredForAllUsers());
@@ -1036,8 +1036,8 @@
permission.setParsedPermissionGroup(new ParsedPermissionGroupImpl());
((ParsedPackage) pkg.setBaseRevisionCode(100)
- .setBaseHardwareAccelerated(true)
- .setSharedUserLabel(100)
+ .setHardwareAccelerated(true)
+ .setSharedUserLabelRes(100)
.setInstallLocation(100)
.setRequiredForAllUsers(true)
.asSplit(
@@ -1062,7 +1062,7 @@
.setSdkLibVersionMajor(42)
.addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
.setStaticSharedLibraryName("foo23")
- .setStaticSharedLibVersion(100)
+ .setStaticSharedLibraryVersion(100)
.addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
.addLibraryName("foo10")
.addUsesLibrary("foo11")
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index b6f1b87..b5bd869 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -273,7 +273,7 @@
public void installStaticSharedLibrary() throws Exception {
final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
.setStaticSharedLibraryName("static.lib")
- .setStaticSharedLibVersion(123L)
+ .setStaticSharedLibraryVersion(123L)
.hideAsParsed())
.setPackageName("static.lib.pkg.123")
.setVersionCodeMajor(1)
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 1c6ba33..9b3b8c35 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -33,11 +33,16 @@
"junit",
"kotlin-test",
"kotlin-reflect",
+ "mockito-target-extended-minus-junit4",
"services.core",
"servicestests-utils",
"servicestests-core-utils",
"truth-prebuilt",
],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
platform_apis: true,
test_suites: ["device-tests"],
}
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
index 2ef7a1f..81f6c82 100644
--- a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
@@ -18,6 +18,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.pm.test">
+ <!--required for using Mockito-->
+ <application android:debuggable="true" />
+
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.server.pm.test"
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index c439639..1619856 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -147,27 +147,27 @@
)
override val baseParams = listOf(
+ AndroidPackage::getApplicationClassName,
AndroidPackage::getAppComponentFactory,
AndroidPackage::getAutoRevokePermissions,
AndroidPackage::getBackupAgentName,
- AndroidPackage::getBanner,
+ AndroidPackage::getBannerRes,
AndroidPackage::getBaseApkPath,
AndroidPackage::getBaseRevisionCode,
AndroidPackage::getCategory,
AndroidPackage::getClassLoaderName,
- AndroidPackage::getClassName,
AndroidPackage::getCompatibleWidthLimitDp,
AndroidPackage::getCompileSdkVersion,
AndroidPackage::getCompileSdkVersionCodeName,
- AndroidPackage::getDataExtractionRules,
+ AndroidPackage::getDataExtractionRulesRes,
AndroidPackage::getDescriptionRes,
- AndroidPackage::getFullBackupContent,
+ AndroidPackage::getFullBackupContentRes,
AndroidPackage::getGwpAsanMode,
AndroidPackage::getIconRes,
AndroidPackage::getInstallLocation,
AndroidPackage::getLabelRes,
AndroidPackage::getLargestWidthLimitDp,
- AndroidPackage::getLogo,
+ AndroidPackage::getLogoRes,
AndroidPackage::getLocaleConfigRes,
AndroidPackage::getManageSpaceActivityName,
AndroidPackage::getMaxSdkVersion,
@@ -195,15 +195,15 @@
PackageImpl::getSecondaryCpuAbi,
AndroidPackage::getSecondaryNativeLibraryDir,
AndroidPackage::getSharedUserId,
- AndroidPackage::getSharedUserLabel,
+ AndroidPackage::getSharedUserLabelRes,
AndroidPackage::getSdkLibraryName,
AndroidPackage::getSdkLibVersionMajor,
AndroidPackage::getStaticSharedLibraryName,
- AndroidPackage::getStaticSharedLibVersion,
+ AndroidPackage::getStaticSharedLibraryVersion,
AndroidPackage::getTargetSandboxVersion,
AndroidPackage::getTargetSdkVersion,
AndroidPackage::getTaskAffinity,
- AndroidPackage::getTheme,
+ AndroidPackage::getThemeRes,
AndroidPackage::getUiOptions,
AndroidPackage::getUid,
AndroidPackage::getVersionName,
@@ -215,7 +215,7 @@
AndroidPackage::isAllowNativeHeapPointerTagging,
AndroidPackage::isAllowTaskReparenting,
AndroidPackage::isBackupInForeground,
- AndroidPackage::isBaseHardwareAccelerated,
+ AndroidPackage::isHardwareAccelerated,
AndroidPackage::isCantSaveState,
AndroidPackage::isCoreApp,
AndroidPackage::isCrossProfile,
@@ -260,7 +260,7 @@
AndroidPackage::isUsesNonSdkApi,
AndroidPackage::isVisibleToInstantApps,
AndroidPackage::isVmSafeMode,
- AndroidPackage::isLeavingSharedUid,
+ AndroidPackage::isLeavingSharedUser,
AndroidPackage::isResetEnabledSettingsOnAppDataCleared,
AndroidPackage::getMaxAspectRatio,
AndroidPackage::getMinAspectRatio,
@@ -293,7 +293,7 @@
adder(AndroidPackage::getUsesOptionalLibraries, "testUsesOptionalLibrary"),
adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
getSetByValue(
- AndroidPackage::areAttributionsUserVisible,
+ AndroidPackage::isAttributionsUserVisible,
PackageImpl::setAttributionsAreUserVisible,
true
),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt
new file mode 100644
index 0000000..52fc91d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.uninstall
+
+import android.app.PackageDeleteObserver
+import android.content.Intent
+import android.content.pm.IPackageDeleteObserver2
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.UninstallCompleteCallback
+import android.os.Parcel
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+class UninstallCompleteCallbackTest {
+
+ val PACKAGE_NAME: String = "com.example.package"
+ val ERROR_MSG: String = "no error"
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ lateinit var mockAdapter: PackageDeleteObserver
+
+ val mockBinder: IPackageDeleteObserver2.Stub = object : IPackageDeleteObserver2.Stub() {
+ override fun onUserActionRequired(intent: Intent) {
+ mockAdapter.onUserActionRequired(intent)
+ }
+ override fun onPackageDeleted(basePackageName: String, returnCode: Int, msg: String) {
+ mockAdapter.onPackageDeleted(basePackageName, returnCode, msg)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ }
+
+ @Test
+ fun testCallDelegation () {
+ doReturn(mockBinder).`when`(mockAdapter).binder
+
+ val callback = UninstallCompleteCallback(mockAdapter.binder.asBinder())
+ callback.onUninstallComplete(PACKAGE_NAME, PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+
+ verify(mockAdapter, times(1)).onPackageDeleted(PACKAGE_NAME,
+ PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+ }
+
+ @Test
+ fun testClassIsParcelable() {
+ doReturn(mockBinder).`when`(mockAdapter).binder
+
+ val callback = UninstallCompleteCallback(mockAdapter.binder.asBinder())
+
+ val parcel = Parcel.obtain()
+ callback.writeToParcel(parcel, callback.describeContents())
+ parcel.setDataPosition(0)
+
+ val callbackFromParcel = UninstallCompleteCallback.CREATOR.createFromParcel(parcel)
+
+ callbackFromParcel.onUninstallComplete(PACKAGE_NAME, PackageManager.DELETE_SUCCEEDED,
+ ERROR_MSG)
+
+ verify(mockAdapter, times(1)).onPackageDeleted(PACKAGE_NAME,
+ PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index cd2f205..3480af6 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -25,11 +28,11 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.backup.BackupAgent;
import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.content.Context;
@@ -37,6 +40,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
+import android.util.FeatureFlagUtils;
import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
@@ -47,15 +51,21 @@
import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.google.common.collect.ImmutableSet;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import java.util.Arrays;
+import java.util.List;
import java.util.function.IntConsumer;
@Presubmit
@@ -63,6 +73,7 @@
public class UserBackupManagerServiceTest {
private static final String TEST_PACKAGE = "package1";
private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
+ private static final String TEST_TRANSPORT = "transport";
private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1;
@Mock Context mContext;
@@ -70,21 +81,38 @@
@Mock IBackupObserver mBackupObserver;
@Mock PackageManager mPackageManager;
@Mock TransportConnection mTransportConnection;
+ @Mock TransportManager mTransportManager;
@Mock BackupTransportClient mBackupTransport;
@Mock BackupEligibilityRules mBackupEligibilityRules;
@Mock LifecycleOperationStorage mOperationStorage;
+ private MockitoSession mSession;
private TestBackupService mService;
@Before
public void setUp() throws Exception {
+ mSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(BackupManagerMonitorUtils.class)
+ .mockStatic(FeatureFlagUtils.class)
+ // TODO(b/263239775): Remove unnecessary stubbing.
+ .strictness(Strictness.LENIENT)
+ .startMocking();
MockitoAnnotations.initMocks(this);
- mService = new TestBackupService(mContext, mPackageManager, mOperationStorage);
+ mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
+ mTransportManager);
mService.setEnabled(true);
mService.setSetupComplete(true);
}
+ @After
+ public void tearDown() {
+ if (mSession != null) {
+ mSession.finishMocking();
+ }
+ }
+
@Test
public void initializeBackupEnableState_doesntWriteStateToDisk() {
mService.initializeBackupEnableState();
@@ -201,6 +229,26 @@
.cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class));
}
+ @Test
+ public void testReportDelayedRestoreResult_sendsLogsToMonitor() throws Exception {
+ PackageInfo packageInfo = getPackageInfo(TEST_PACKAGE);
+ when(mPackageManager.getPackageInfoAsUser(anyString(),
+ any(PackageManager.PackageInfoFlags.class), anyInt())).thenReturn(packageInfo);
+ when(mTransportManager.getCurrentTransportName()).thenReturn(TEST_TRANSPORT);
+ when(mTransportManager.getTransportClientOrThrow(eq(TEST_TRANSPORT), anyString()))
+ .thenReturn(mTransportConnection);
+ when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport);
+ when(mBackupTransport.getBackupManagerMonitor()).thenReturn(mBackupManagerMonitor);
+
+
+ List<DataTypeResult> results = Arrays.asList(new DataTypeResult(/* dataType */ "type_1"),
+ new DataTypeResult(/* dataType */ "type_2"));
+ mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
+
+ verify(() -> BackupManagerMonitorUtils.sendAgentLoggingResults(
+ eq(mBackupManagerMonitor), eq(packageInfo), eq(results)));
+ }
+
private static PackageInfo getPackageInfo(String packageName) {
PackageInfo packageInfo = new PackageInfo();
packageInfo.applicationInfo = new ApplicationInfo();
@@ -215,8 +263,8 @@
private volatile Thread mWorkerThread = null;
TestBackupService(Context context, PackageManager packageManager,
- LifecycleOperationStorage operationStorage) {
- super(context, packageManager, operationStorage);
+ LifecycleOperationStorage operationStorage, TransportManager transportManager) {
+ super(context, packageManager, operationStorage, transportManager);
}
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index c4ff4f0..a8b8f91 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -254,6 +254,12 @@
ConnectivityController connectivityController = mService.getConnectivityController();
spyOn(connectivityController);
+ mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 15 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
+ mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+ mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+ mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -268,7 +274,7 @@
assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDef));
grantRunLongJobsPermission(false); // Without permission
- assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDT));
grantRunLongJobsPermission(true); // With permission
doReturn(ConnectivityController.UNKNOWN_TIME)
@@ -288,12 +294,16 @@
assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobDT));
// UserInitiated
+ grantRunLongJobsPermission(false);
+ // Permission isn't granted, so it should just be treated as a regular data transfer job.
+ assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+ // Permission isn't granted, so it should just be treated as a regular job.
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUI));
+ grantRunLongJobsPermission(true); // With permission
assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(jobUI));
- grantRunLongJobsPermission(false);
- assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
- mService.getMinJobExecutionGuaranteeMs(jobUIDT));
- grantRunLongJobsPermission(true); // With permission
doReturn(ConnectivityController.UNKNOWN_TIME)
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 28c78b2..cffd027 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -391,7 +391,7 @@
libraries?.forEach { pkg.addLibraryName(it) }
staticLibrary?.let {
pkg.setStaticSharedLibraryName(it)
- pkg.setStaticSharedLibVersion(staticLibraryVersion)
+ pkg.setStaticSharedLibraryVersion(staticLibraryVersion)
pkg.setStaticSharedLibrary(true)
}
usesLibraries?.forEach { pkg.addUsesLibrary(it) }
@@ -435,7 +435,7 @@
libraries?.forEach { addLibraryName(it) }
staticLibrary?.let {
setStaticSharedLibraryName(it)
- setStaticSharedLibVersion(staticLibraryVersion)
+ setStaticSharedLibraryVersion(staticLibraryVersion)
setStaticSharedLibrary(true)
}
usesLibraries?.forEach { addUsesLibrary(it) }
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
index cadc890..87ade96 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
@@ -172,11 +172,30 @@
.when(agent)
.getLoggerResults(any());
- IBackupManagerMonitor result =
+ IBackupManagerMonitor monitor =
BackupManagerMonitorUtils.monitorAgentLoggingResults(
mMonitorMock, packageInfo, agent);
- assertThat(result).isEqualTo(mMonitorMock);
+ assertCorrectBundleSentToMonitor(monitor);
+ }
+
+ @Test
+ public void sendAgentLoggingResults_fillsBundleCorrectly() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test.package";
+ List<BackupRestoreEventLogger.DataTypeResult> loggingResults = new ArrayList<>();
+ loggingResults.add(new BackupRestoreEventLogger.DataTypeResult("testLoggingResult"));
+
+ IBackupManagerMonitor monitor = BackupManagerMonitorUtils.sendAgentLoggingResults(
+ mMonitorMock, packageInfo, loggingResults);
+
+ assertCorrectBundleSentToMonitor(monitor);
+ }
+
+ private void assertCorrectBundleSentToMonitor(IBackupManagerMonitor monitor) throws Exception {
+
+
+ assertThat(monitor).isEqualTo(mMonitorMock);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mMonitorMock).onEvent(bundleCaptor.capture());
Bundle eventBundle = bundleCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index bd4058a..69a0b87 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -40,6 +40,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -73,6 +74,7 @@
import android.test.mock.MockContentResolver;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.Display;
import android.view.SurfaceControl.RefreshRateRange;
import android.view.SurfaceControl.RefreshRateRanges;
@@ -102,6 +104,7 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -2293,6 +2296,61 @@
new int[]{20});
}
+ @Test
+ public void testSensorReloadOnDeviceSwitch() throws Exception {
+ // First, configure brightness zones or DMD won't register for sensor data.
+ final FakeDeviceConfig config = mInjector.getDeviceConfig();
+ config.setRefreshRateInHighZone(60);
+ config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+ config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+ setPeakRefreshRate(90 /*fps*/);
+ director.getSettingsObserver().setDefaultRefreshRate(90);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Sensor lightSensorOne = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor lightSensorTwo = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ SensorManager sensorManager = createMockSensorManager(lightSensorOne, lightSensorTwo);
+ when(sensorManager.getDefaultSensor(5)).thenReturn(lightSensorOne, lightSensorTwo);
+ director.start(sensorManager);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+ .registerListener(
+ listenerCaptor.capture(),
+ eq(lightSensorOne),
+ anyInt(),
+ any(Handler.class));
+
+ DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+ when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
+ when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
+ when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+ when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+ when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+ when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
+
+ Resources resMock = mock(Resources.class);
+ when(resMock.getInteger(
+ com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+ .thenReturn(3);
+ ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
+ doAnswer((Answer<Void>) invocation -> {
+ valueArgumentCaptor.getValue().type = 4;
+ valueArgumentCaptor.getValue().data = 13;
+ return null;
+ }).when(resMock).getValue(anyInt(), valueArgumentCaptor.capture(), eq(true));
+ when(mContext.getResources()).thenReturn(resMock);
+
+ director.defaultDisplayDeviceUpdated(ddcMock);
+
+ verify(sensorManager).unregisterListener(any(SensorEventListener.class));
+ verify(sensorManager).registerListener(any(SensorEventListener.class),
+ eq(lightSensorTwo), anyInt(), any(Handler.class));
+ }
+
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index d90f53a..1c44da1 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,6 +6,8 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -15,6 +17,7 @@
import android.app.job.JobInfo;
import android.app.job.JobInfo.Builder;
+import android.app.job.JobWorkItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
@@ -31,6 +34,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobSet;
import com.android.server.job.controllers.JobStatus;
@@ -43,6 +47,10 @@
import java.io.File;
import java.time.Clock;
import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
/**
* Test reading and writing correctly from file.
@@ -690,20 +698,46 @@
taskStatus.getJob().isRequireBatteryNotLow());
}
+ @Test
+ public void testJobWorkItems() throws Exception {
+ JobWorkItem item1 = new JobWorkItem.Builder().build();
+ item1.bumpDeliveryCount();
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean("test", true);
+ JobWorkItem item2 = new JobWorkItem.Builder().setExtras(bundle).build();
+ item2.bumpDeliveryCount();
+ JobWorkItem item3 = new JobWorkItem.Builder().setEstimatedNetworkBytes(1, 2).build();
+ JobWorkItem item4 = new JobWorkItem.Builder().setMinimumNetworkChunkBytes(3).build();
+ JobWorkItem item5 = new JobWorkItem.Builder().build();
+
+ JobInfo jobInfo = new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .build();
+ JobStatus jobStatus =
+ JobStatus.createFromJobInfo(jobInfo, SOME_UID, null, -1, null, null);
+ jobStatus.executingWork = new ArrayList<>(List.of(item1, item2));
+ jobStatus.pendingWork = new ArrayList<>(List.of(item3, item4, item5));
+ assertPersistedEquals(jobStatus);
+ }
+
/**
* Helper function to kick a {@link JobInfo} through a persistence cycle and
* assert that it's unchanged.
*/
private void assertPersistedEquals(JobInfo firstInfo) throws Exception {
+ assertPersistedEquals(
+ JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null));
+ }
+
+ private void assertPersistedEquals(JobStatus original) throws Exception {
mTaskStoreUnderTest.clear();
- JobStatus first = JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null);
- mTaskStoreUnderTest.add(first);
+ mTaskStoreUnderTest.add(original);
waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
- assertJobsEqual(first, second);
+ assertJobsEqual(original, second);
}
/**
@@ -729,6 +763,59 @@
expected.getEarliestRunTime(), actual.getEarliestRunTime());
compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
+
+ assertEquals(expected.hasWorkLocked(), actual.hasWorkLocked());
+ if (expected.hasWorkLocked()) {
+ List<JobWorkItem> allWork = new ArrayList<>();
+ if (expected.executingWork != null) {
+ allWork.addAll(expected.executingWork);
+ }
+ if (expected.pendingWork != null) {
+ allWork.addAll(expected.pendingWork);
+ }
+ // All work for freshly loaded Job will be pending.
+ assertNotNull(actual.pendingWork);
+ assertTrue(ArrayUtils.isEmpty(actual.executingWork));
+ assertEquals(allWork.size(), actual.pendingWork.size());
+ for (int i = 0; i < allWork.size(); ++i) {
+ JobWorkItem expectedItem = allWork.get(i);
+ JobWorkItem actualItem = actual.pendingWork.get(i);
+ assertJobWorkItemsEqual(expectedItem, actualItem);
+ }
+ }
+ }
+
+ private void assertJobWorkItemsEqual(JobWorkItem expected, JobWorkItem actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.getDeliveryCount(), actual.getDeliveryCount());
+ assertEquals(expected.getEstimatedNetworkDownloadBytes(),
+ actual.getEstimatedNetworkDownloadBytes());
+ assertEquals(expected.getEstimatedNetworkUploadBytes(),
+ actual.getEstimatedNetworkUploadBytes());
+ assertEquals(expected.getMinimumNetworkChunkBytes(), actual.getMinimumNetworkChunkBytes());
+ if (expected.getIntent() == null) {
+ assertNull(actual.getIntent());
+ } else {
+ // filterEquals() just so happens to check almost everything that is persisted to disk.
+ assertTrue(expected.getIntent().filterEquals(actual.getIntent()));
+ assertEquals(expected.getIntent().getFlags(), actual.getIntent().getFlags());
+ }
+ assertEquals(expected.getGrants(), actual.getGrants());
+ PersistableBundle expectedExtras = expected.getExtras();
+ PersistableBundle actualExtras = actual.getExtras();
+ if (expectedExtras == null) {
+ assertNull(actualExtras);
+ } else {
+ assertEquals(expectedExtras.size(), actualExtras.size());
+ Set<String> keys = expectedExtras.keySet();
+ for (String key : keys) {
+ assertTrue(Objects.equals(expectedExtras.get(key), actualExtras.get(key)));
+ }
+ }
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
index 6cdae53..56df9d9 100644
--- a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
+++ b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
@@ -16,6 +16,8 @@
package com.android.server.locales;
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
import android.annotation.Nullable;
import android.os.LocaleList;
@@ -29,6 +31,8 @@
FakePackageConfigurationUpdater() {}
+ private int mGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
LocaleList mLocales = null;
@Override
@@ -43,6 +47,12 @@
}
@Override
+ public PackageConfigurationUpdater setGrammaticalGender(int gender) {
+ mGender = gender;
+ return this;
+ }
+
+ @Override
public boolean commit() {
return mLocales != null;
}
@@ -56,4 +66,10 @@
return mLocales;
}
+ /**
+ * Returns the gender that were stored during the test run.
+ */
+ int getGender() {
+ return mGender;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 79ed7d1..065aec5 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.locales;
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -234,7 +236,8 @@
throws Exception {
doReturn(DEFAULT_UID).when(mMockPackageManager)
.getPackageUidAsUser(anyString(), any(), anyInt());
- doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+ GRAMMATICAL_GENDER_NOT_SPECIFIED))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
String imPkgName = getCurrentInputMethodPackageName();
doReturn(Binder.getCallingUid()).when(mMockPackageManager)
@@ -274,7 +277,8 @@
doReturn(DEFAULT_UID).when(mMockPackageManager)
.getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
- doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
+ doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null,
+ GRAMMATICAL_GENDER_NOT_SPECIFIED))
.when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
LocaleList locales = mLocaleManagerService.getApplicationLocales(
@@ -288,7 +292,8 @@
throws Exception {
doReturn(Binder.getCallingUid()).when(mMockPackageManager)
.getPackageUidAsUser(anyString(), any(), anyInt());
- doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+ GRAMMATICAL_GENDER_NOT_SPECIFIED))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
LocaleList locales =
@@ -303,7 +308,8 @@
doReturn(DEFAULT_UID).when(mMockPackageManager)
.getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
- doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+ GRAMMATICAL_GENDER_NOT_SPECIFIED))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
LocaleList locales =
@@ -319,7 +325,8 @@
.getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
doReturn(Binder.getCallingUid()).when(mMockPackageManager)
.getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
- doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+ GRAMMATICAL_GENDER_NOT_SPECIFIED))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
LocaleList locales =
@@ -334,7 +341,8 @@
throws Exception {
doReturn(DEFAULT_UID).when(mMockPackageManager)
.getPackageUidAsUser(anyString(), any(), anyInt());
- doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+ GRAMMATICAL_GENDER_NOT_SPECIFIED))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
String imPkgName = getCurrentInputMethodPackageName();
doReturn(Binder.getCallingUid()).when(mMockPackageManager)
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index cbf555e..494796e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -16,6 +16,8 @@
package com.android.server.locales;
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
@@ -168,7 +170,8 @@
/* isUpdatedSystemApp = */ true))
.when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
- DEFAULT_LOCALES)).when(mMockActivityTaskManager)
+ DEFAULT_LOCALES, GRAMMATICAL_GENDER_NOT_SPECIFIED))
+ .when(mMockActivityTaskManager)
.getApplicationConfig(anyString(), anyInt());
mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
@@ -186,7 +189,8 @@
/* isUpdatedSystemApp = */ true))
.when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
- DEFAULT_LOCALES)).when(mMockActivityTaskManager)
+ DEFAULT_LOCALES, GRAMMATICAL_GENDER_NOT_SPECIFIED))
+ .when(mMockActivityTaskManager)
.getApplicationConfig(anyString(), anyInt());
// first update
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index b16ca8b..b4a294d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -19,6 +19,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
+import android.os.Parcel;
import android.service.notification.ZenPolicy;
import android.service.notification.nano.DNDPolicyProto;
import android.test.suitebuilder.annotation.SmallTest;
@@ -32,9 +33,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ZenPolicyTest extends UiServiceTestCase {
+ private static final String CLASS = "android.service.notification.ZenPolicy";
@Test
public void testZenPolicyApplyAllowedToDisallowed() {
@@ -524,6 +529,66 @@
assertProtoMatches(policy, policy.toProto());
}
+ @Test
+ public void testTooLongLists_fromParcel() {
+ ArrayList<Integer> longList = new ArrayList<Integer>(50);
+ for (int i = 0; i < 50; i++) {
+ longList.add(ZenPolicy.STATE_UNSET);
+ }
+
+ ZenPolicy.Builder builder = new ZenPolicy.Builder();
+ ZenPolicy policy = builder.build();
+
+ try {
+ Field priorityCategories = Class.forName(CLASS).getDeclaredField(
+ "mPriorityCategories");
+ priorityCategories.setAccessible(true);
+ priorityCategories.set(policy, longList);
+
+ Field visualEffects = Class.forName(CLASS).getDeclaredField("mVisualEffects");
+ visualEffects.setAccessible(true);
+ visualEffects.set(policy, longList);
+ } catch (NoSuchFieldException e) {
+ fail(e.toString());
+ } catch (ClassNotFoundException e) {
+ fail(e.toString());
+ } catch (IllegalAccessException e) {
+ fail(e.toString());
+ }
+
+ Parcel parcel = Parcel.obtain();
+ policy.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
+
+ // Confirm that all the fields are accessible and UNSET
+ assertAllPriorityCategoriesUnsetExcept(fromParcel, -1);
+ assertAllVisualEffectsUnsetExcept(fromParcel, -1);
+
+ // Because we don't access the lists directly, we also need to use reflection to make sure
+ // the lists are the right length.
+ try {
+ Field priorityCategories = Class.forName(CLASS).getDeclaredField(
+ "mPriorityCategories");
+ priorityCategories.setAccessible(true);
+ ArrayList<Integer> pcList = (ArrayList<Integer>) priorityCategories.get(fromParcel);
+ assertEquals(ZenPolicy.NUM_PRIORITY_CATEGORIES, pcList.size());
+
+
+ Field visualEffects = Class.forName(CLASS).getDeclaredField("mVisualEffects");
+ visualEffects.setAccessible(true);
+ ArrayList<Integer> veList = (ArrayList<Integer>) visualEffects.get(fromParcel);
+ assertEquals(ZenPolicy.NUM_VISUAL_EFFECTS, veList.size());
+ } catch (NoSuchFieldException e) {
+ fail(e.toString());
+ } catch (ClassNotFoundException e) {
+ fail(e.toString());
+ } catch (IllegalAccessException e) {
+ fail(e.toString());
+ }
+ }
+
private void assertAllPriorityCategoriesUnsetExcept(ZenPolicy policy, int except) {
if (except != ZenPolicy.PRIORITY_CATEGORY_REMINDERS) {
assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryReminders());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 92dd047..4ad8516 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.view.Display.INVALID_DISPLAY;
@@ -281,6 +280,64 @@
}
@Test
+ public void testStartRecording_notifiesCallback() {
+ // WHEN a recording is ongoing.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper)
+ .notifyActiveProjectionCapturedContentVisibilityChanged(true);
+ }
+
+ @Test
+ public void testOnVisibleRequestedChanged_notifiesCallback() {
+ // WHEN a recording is ongoing.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // WHEN the child requests a visibility change.
+ boolean isVisibleRequested = true;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper, atLeastOnce())
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+ // WHEN the child requests a visibility change.
+ isVisibleRequested = false;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is notified.
+ verify(mMediaProjectionManagerWrapper)
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+ }
+
+ @Test
+ public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+ // WHEN a recording is not ongoing.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+ // WHEN the child requests a visibility change.
+ boolean isVisibleRequested = true;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is not notified.
+ verify(mMediaProjectionManagerWrapper, never())
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+ // WHEN the child requests a visibility change.
+ isVisibleRequested = false;
+ mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+ // THEN the visibility change callback is not notified.
+ verify(mMediaProjectionManagerWrapper, never())
+ .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+ }
+
+ @Test
public void testPauseRecording_pausesRecording() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
new file mode 100644
index 0000000..d1234e3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.Surface.Rotation;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for {@link DisplayRotationCompatPolicy}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayRotationCompatPolicyTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
+
+ private static final String TEST_PACKAGE_1 = "com.test.package.one";
+ private static final String TEST_PACKAGE_2 = "com.test.package.two";
+ private static final String CAMERA_ID_1 = "camera-1";
+ private static final String CAMERA_ID_2 = "camera-2";
+
+ private CameraManager mMockCameraManager;
+ private Handler mMockHandler;
+ private LetterboxConfiguration mLetterboxConfiguration;
+
+ private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
+ private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
+
+ private ActivityRecord mActivity;
+ private Task mTask;
+
+ @Before
+ public void setUp() throws Exception {
+ mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+ spyOn(mLetterboxConfiguration);
+ when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ anyBoolean()))
+ .thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+ .thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(true);
+
+ mMockCameraManager = mock(CameraManager.class);
+ doAnswer(invocation -> {
+ mCameraAvailabilityCallback = invocation.getArgument(1);
+ return null;
+ }).when(mMockCameraManager).registerAvailabilityCallback(
+ any(Executor.class), any(CameraManager.AvailabilityCallback.class));
+
+ spyOn(mContext);
+ when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager);
+
+ spyOn(mDisplayContent);
+
+ mDisplayContent.setIgnoreOrientationRequest(true);
+
+ mMockHandler = mock(Handler.class);
+
+ when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+ invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ });
+ mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
+ mDisplayContent, mMockHandler);
+ }
+
+ @Test
+ public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ anyBoolean()))
+ .thenReturn(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ true))
+ .thenReturn(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent);
+ mActivity.getTask().reparent(organizer.mPrimary, WindowContainer.POSITION_TOP,
+ false /* moveParents */, "test" /* reason */);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mActivity.inMultiWindowMode());
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testOrientationUnspecified_noForceRotationOrRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testOrientationLocked_noForceRotationOrRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_LOCKED);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testOrientationNoSensor_noForceRotationOrRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_NOSENSOR);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testIgnoreOrientationRequestIsFalse_noForceRotationOrRefresh() throws Exception {
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testDisplayNotInternal_noForceRotationOrRefresh() throws Exception {
+ Display display = mDisplayContent.getDisplay();
+ spyOn(display);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ when(display.getType()).thenReturn(Display.TYPE_EXTERNAL);
+ assertNoForceRotationOrRefresh();
+
+ when(display.getType()).thenReturn(Display.TYPE_WIFI);
+ assertNoForceRotationOrRefresh();
+
+ when(display.getType()).thenReturn(Display.TYPE_OVERLAY);
+ assertNoForceRotationOrRefresh();
+
+ when(display.getType()).thenReturn(Display.TYPE_VIRTUAL);
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testNoCameraConnection_noForceRotationOrRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testCameraReconnected_forceRotationAndRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_PORTRAIT);
+ assertActivityRefreshRequested(/* refreshRequested */ true);
+ }
+
+ @Test
+ public void testReconnectedToDifferentCamera_forceRotationAndRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_PORTRAIT);
+ assertActivityRefreshRequested(/* refreshRequested */ true);
+ }
+
+ @Test
+ public void testGetOrientation_cameraConnectionClosed_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testCameraOpenedForDifferentPackage_noForceRotationOrRefresh() throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+
+ assertNoForceRotationOrRefresh();
+ }
+
+ @Test
+ public void testGetOrientation_portraitActivity_portraitNaturalOrientation_returnPortrait() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+ /* naturalOrientation */ ORIENTATION_PORTRAIT,
+ /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ @Test
+ public void testGetOrientation_portraitActivity_landscapeNaturalOrientation_returnLandscape() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+ /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+ /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
+ public void testGetOrientation_landscapeActivity_portraitNaturalOrientation_returnLandscape() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+ /* naturalOrientation */ ORIENTATION_PORTRAIT,
+ /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
+ public void testGetOrientation_landscapeActivity_landscapeNaturalOrientation_returnPortrait() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+ /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+ /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ private void testGetOrientationForActivityAndNaturalOrientations(
+ @ScreenOrientation int activityOrientation,
+ @Orientation int naturalOrientation,
+ @ScreenOrientation int expectedOrientation) {
+ configureActivityAndDisplay(activityOrientation, naturalOrientation);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ expectedOrientation);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_displayRotationNotChanging_noRefresh()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
+
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
+ private void configureActivity(@ScreenOrientation int activityOrientation) {
+ configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
+ }
+
+ private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
+ @Orientation int naturalOrientation) {
+
+ mTask = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent)
+ .build();
+
+ mActivity = new ActivityBuilder(mAtm)
+ .setComponent(new ComponentName(TEST_PACKAGE_1, ".TestActivity"))
+ .setScreenOrientation(activityOrientation)
+ .setTask(mTask)
+ .build();
+
+ spyOn(mActivity.mAtmService.getLifecycleManager());
+ spyOn(mActivity.mLetterboxUiController);
+
+ doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
+ doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+ assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested,
+ boolean cycleThroughStop) throws Exception {
+ verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+ .setIsRefreshAfterRotationRequested(true);
+
+ final ClientTransaction transaction = ClientTransaction.obtain(
+ mActivity.app.getThread(), mActivity.token);
+ transaction.addCallback(RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+ transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+
+ verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+ .scheduleTransaction(eq(transaction));
+ }
+
+ private void assertNoForceRotationOrRefresh() throws Exception {
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ private void callOnActivityConfigurationChanging(
+ ActivityRecord activity, boolean isDisplayRotationChanging) {
+ mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+ /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
+ /* newConfig */ createConfigurationWithDisplayRotation(
+ isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
+ }
+
+ private static Configuration createConfigurationWithDisplayRotation(@Rotation int rotation) {
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setDisplayRotation(rotation);
+ return config;
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index d583e89..1a1ca54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -56,6 +56,8 @@
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -660,6 +662,111 @@
}
@Test
+ public void testSetVisibleRequested() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ assertThat(root.isVisibleRequested()).isFalse();
+ final TestWindowContainerListener listener = new TestWindowContainerListener();
+ root.registerWindowContainerListener(listener);
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(root.isVisibleRequested()).isFalse();
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(root.isVisibleRequested()).isTrue();
+ assertThat(listener.mIsVisibleRequested).isTrue();
+ }
+
+ @Test
+ public void testSetVisibleRequested_childRequestsVisible() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+ assertThat(child1.isVisibleRequested()).isFalse();
+ final TestWindowContainerListener listener = new TestWindowContainerListener();
+ root.registerWindowContainerListener(listener);
+
+ // Hidden root and child request hidden.
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(listener.mIsVisibleRequested).isFalse();
+ assertThat(child1.isVisibleRequested()).isFalse();
+
+ // Child requests to be visible, so child and root request visible.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(root.isVisibleRequested()).isTrue();
+ assertThat(listener.mIsVisibleRequested).isTrue();
+ assertThat(child1.isVisibleRequested()).isTrue();
+ // Visible request didn't change.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isFalse();
+ verify(root, times(2)).onChildVisibleRequestedChanged(child1);
+ }
+
+ @Test
+ public void testSetVisibleRequested_childRequestsHidden() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+ assertThat(child1.isVisibleRequested()).isFalse();
+ final TestWindowContainerListener listener = new TestWindowContainerListener();
+ root.registerWindowContainerListener(listener);
+
+ // Root and child requests visible.
+ assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(listener.mIsVisibleRequested).isTrue();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(child1.isVisibleRequested()).isTrue();
+
+ // Child requests hidden, so child and root request hidden.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isTrue();
+ assertThat(root.isVisibleRequested()).isFalse();
+ assertThat(listener.mIsVisibleRequested).isFalse();
+ assertThat(child1.isVisibleRequested()).isFalse();
+ // Visible request didn't change.
+ assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ verify(root, times(3)).onChildVisibleRequestedChanged(child1);
+ }
+
+ @Test
+ public void testOnChildVisibleRequestedChanged_bothVisible() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ // Child and root request visible.
+ assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+ // Visible request already updated on root when child requested.
+ assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+ }
+
+ @Test
+ public void testOnChildVisibleRequestedChanged_childVisible() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+ // Visible request already updated on root when child requested.
+ assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+ }
+
+ @Test
+ public void testOnChildVisibleRequestedChanged_childHidden() {
+ final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+ 0).build());
+ final TestWindowContainer child1 = root.addChildWindow();
+
+ assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+ assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+
+ // Visible request did not change.
+ assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+ }
+
+ @Test
public void testSetOrientation() {
final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).build());
final TestWindowContainer child = spy(root.addChildWindow());
@@ -1656,6 +1763,7 @@
private static class TestWindowContainerListener implements WindowContainerListener {
private Configuration mConfiguration = new Configuration();
private DisplayContent mDisplayContent;
+ private boolean mIsVisibleRequested;
@Override
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
@@ -1666,5 +1774,10 @@
public void onDisplayChanged(DisplayContent dc) {
mDisplayContent = dc;
}
+
+ @Override
+ public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+ mIsVisibleRequested = isVisibleRequested;
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 8bd4148..d6cfd00 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -414,9 +415,10 @@
public void testTopActivityUiModeChangeScheduleConfigChange() {
final ActivityRecord activity = createActivityRecord(mWpc);
activity.setVisibleRequested(true);
- doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
+ doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any(), anyInt());
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
- Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
+ Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
+ GRAMMATICAL_GENDER_NOT_SPECIFIED);
verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
}
@@ -425,8 +427,9 @@
final ActivityRecord activity = createActivityRecord(mWpc);
activity.setVisibleRequested(true);
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
- Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
- verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
+ Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
+ GRAMMATICAL_GENDER_NOT_SPECIFIED);
+ verify(activity, never()).applyAppSpecificConfig(anyInt(), any(), anyInt());
verify(activity, never()).ensureActivityConfiguration(anyInt(), anyBoolean());
}
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index fccdf76..c7cc1bd 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -27,7 +27,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -58,6 +57,9 @@
/** Direct the audio stream through the device's speakerphone. */
public static final int ROUTE_SPEAKER = 0x00000008;
+ /** Direct the audio stream through another device. */
+ public static final int ROUTE_STREAMING = 0x00000010;
+
/**
* Direct the audio stream through the device's earpiece or wired headset if one is
* connected.
@@ -70,7 +72,7 @@
* @hide
**/
public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
- ROUTE_SPEAKER;
+ ROUTE_SPEAKER | ROUTE_STREAMING;
private final boolean isMuted;
private final int route;
@@ -189,7 +191,11 @@
*/
@CallAudioRoute
public int getSupportedRouteMask() {
- return supportedRouteMask;
+ if (route == ROUTE_STREAMING) {
+ return ROUTE_STREAMING;
+ } else {
+ return supportedRouteMask;
+ }
}
/**
@@ -232,6 +238,9 @@
if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
listAppend(buffer, "SPEAKER");
}
+ if ((route & ROUTE_STREAMING) == ROUTE_STREAMING) {
+ listAppend(buffer, "STREAMING");
+ }
return buffer.toString();
}
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 3bda6f4..867bcc7 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -191,6 +191,38 @@
}
/**
+ * Request start a call streaming session. On receiving valid request, telecom will bind to
+ * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
+ * call streaming sender can perform streaming local device audio to another remote device and
+ * control the call during streaming.
+ *
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback that will be completed on the Telecom side that details success or failure
+ * of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+ * rejected the incoming call.
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+ * reject the incoming call. A {@link CallException} will be passed that
+ * details why the operation failed.
+ */
+ public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.startCallStreaming(mCallId,
+ new CallControlResultReceiver("startCallStreaming", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
* This method should be called after
* {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
* {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
diff --git a/telecomm/java/android/telecom/CallEventCallback.java b/telecomm/java/android/telecom/CallEventCallback.java
index a26291f..fd7e101 100644
--- a/telecomm/java/android/telecom/CallEventCallback.java
+++ b/telecomm/java/android/telecom/CallEventCallback.java
@@ -100,4 +100,22 @@
* @param callAudioState that is currently being used
*/
void onCallAudioStateChanged(@NonNull CallAudioState callAudioState);
+
+ /**
+ * Telecom is informing the client to set the call in streaming.
+ *
+ * @param wasCompleted The {@link Consumer} to be completed. If the client can stream the
+ * call on their end, {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+ * should be called with {@link Boolean#FALSE}.
+ */
+ void onCallStreamingStarted(@NonNull Consumer<Boolean> wasCompleted);
+
+ /**
+ * Telecom is informing the client user requested call streaming but the stream can't be
+ * started.
+ *
+ * @param reason Code to indicate the reason of this failure
+ */
+ void onCallStreamingFailed(@CallStreamingService.StreamingFailedReason int reason);
}
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
new file mode 100644
index 0000000..affa6b6
--- /dev/null
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.telecom.ICallStreamingService;
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This service is implemented by an app that wishes to provide functionality for a general call
+ * streaming sender for voip calls.
+ *
+ * TODO: add doc of how to be the general streaming sender
+ *
+ */
+public abstract class CallStreamingService extends Service {
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+
+ private static final int MSG_SET_STREAMING_CALL_ADAPTER = 1;
+ private static final int MSG_CALL_STREAMING_STARTED = 2;
+ private static final int MSG_CALL_STREAMING_STOPPED = 3;
+ private static final int MSG_CALL_STREAMING_CHANGED_CHANGED = 4;
+
+ /** Default Handler used to consolidate binder method calls onto a single thread. */
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mStreamingCallAdapter == null && msg.what != MSG_SET_STREAMING_CALL_ADAPTER) {
+ return;
+ }
+
+ switch (msg.what) {
+ case MSG_SET_STREAMING_CALL_ADAPTER:
+ mStreamingCallAdapter = new StreamingCallAdapter(
+ (IStreamingCallAdapter) msg.obj);
+ break;
+ case MSG_CALL_STREAMING_STARTED:
+ mCall = (StreamingCall) msg.obj;
+ mCall.setAdapter(mStreamingCallAdapter);
+ CallStreamingService.this.onCallStreamingStarted(mCall);
+ break;
+ case MSG_CALL_STREAMING_STOPPED:
+ mCall = null;
+ mStreamingCallAdapter = null;
+ CallStreamingService.this.onCallStreamingStopped();
+ break;
+ case MSG_CALL_STREAMING_CHANGED_CHANGED:
+ int state = (int) msg.obj;
+ mCall.setStreamingState(state);
+ CallStreamingService.this.onCallStreamingStateChanged(state);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ @Nullable
+ @Override
+ public IBinder onBind(@NonNull Intent intent) {
+ return new CallStreamingServiceBinder();
+ }
+
+ /** Manages the binder calls so that the implementor does not need to deal with it. */
+ private final class CallStreamingServiceBinder extends ICallStreamingService.Stub {
+ @Override
+ public void setStreamingCallAdapter(IStreamingCallAdapter streamingCallAdapter)
+ throws RemoteException {
+ mHandler.obtainMessage(MSG_SET_STREAMING_CALL_ADAPTER, mStreamingCallAdapter)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onCallStreamingStarted(StreamingCall call) throws RemoteException {
+ mHandler.obtainMessage(MSG_CALL_STREAMING_STARTED, call).sendToTarget();
+ }
+
+ @Override
+ public void onCallStreamingStopped() throws RemoteException {
+ mHandler.obtainMessage(MSG_CALL_STREAMING_STOPPED).sendToTarget();
+ }
+
+ @Override
+ public void onCallStreamingStateChanged(int state) throws RemoteException {
+ mHandler.obtainMessage(MSG_CALL_STREAMING_CHANGED_CHANGED, state).sendToTarget();
+ }
+ }
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request because there's an ongoing streaming call on this device.
+ */
+ public static final int STREAMING_FAILED_ALREADY_STREAMING = 1;
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request because telecom can't find existing general streaming sender on this
+ * device.
+ */
+ public static final int STREAMING_FAILED_NO_SENDER = 2;
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request because telecom can't bind to the general streaming sender app.
+ */
+ public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3;
+
+ private StreamingCallAdapter mStreamingCallAdapter;
+ private StreamingCall mCall;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"STREAMING_FAILED"},
+ value = {
+ STREAMING_FAILED_ALREADY_STREAMING,
+ STREAMING_FAILED_NO_SENDER,
+ STREAMING_FAILED_SENDER_BINDING_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StreamingFailedReason {};
+
+ /**
+ * Called when a {@code StreamingCall} has been added to this call streaming session. The call
+ * streaming sender should start to intercept the device audio using audio records and audio
+ * tracks from Audio frameworks.
+ *
+ * @param call a newly added {@code StreamingCall}.
+ */
+ public void onCallStreamingStarted(@NonNull StreamingCall call) {
+ }
+
+ /**
+ * Called when a current {@code StreamingCall} has been removed from this call streaming
+ * session. The call streaming sender should notify the streaming receiver that the call is
+ * stopped streaming and stop the device audio interception.
+ */
+ public void onCallStreamingStopped() {
+ }
+
+ /**
+ * Called when the streaming state of current {@code StreamingCall} changed. General streaming
+ * sender usually get notified of the holding/unholding from the original owner voip app of the
+ * call.
+ */
+ public void onCallStreamingStateChanged(@StreamingCall.StreamingCallState int state) {
+ }
+}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 047ab3a..b8c056e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -437,7 +437,15 @@
*/
public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 0x40000;
- /* NEXT CAPABILITY: [0x80000, 0x100000, 0x200000] */
+ /**
+ * Flag indicating that this voip app {@link PhoneAccount} supports the call streaming session
+ * to stream call audio to another remote device via streaming app.
+ *
+ * @see #getCapabilities
+ */
+ public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 0x80000;
+
+ /* NEXT CAPABILITY: [0x100000, 0x200000, 0x400000] */
/**
* URI scheme for telephone number URIs.
diff --git a/telecomm/java/android/telecom/StreamingCall.aidl b/telecomm/java/android/telecom/StreamingCall.aidl
new file mode 100644
index 0000000..d286658
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable StreamingCall;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
new file mode 100644
index 0000000..985cccc
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a voip call requested to stream to another device that the general streaming sender
+ * app should present to the receiver.
+ */
+public final class StreamingCall implements Parcelable {
+ /**
+ * The state of a {@code StreamingCall} when newly created. General streaming sender should
+ * continuously stream call audio to the sender device as long as the {@code StreamingCall} is
+ * in this state.
+ */
+ public static final int STATE_STREAMING = 1;
+
+ /**
+ * The state of a {@code StreamingCall} when in a holding state.
+ */
+ public static final int STATE_HOLDING = 2;
+
+ /**
+ * The state of a {@code StreamingCall} when it's either disconnected or pulled back to the
+ * original device.
+ */
+ public static final int STATE_DISCONNECTED = 3;
+
+ private StreamingCall(@NonNull Parcel in) {
+ mComponentName = in.readParcelable(ComponentName.class.getClassLoader());
+ mDisplayName = in.readString16NoHelper();
+ mAddress = in.readParcelable(Uri.class.getClassLoader());
+ mExtras = in.readBundle();
+ mState = in.readInt();
+ }
+
+ @NonNull
+ public static final Creator<StreamingCall> CREATOR = new Creator<>() {
+ @Override
+ public StreamingCall createFromParcel(@NonNull Parcel in) {
+ return new StreamingCall(in);
+ }
+
+ @Override
+ public StreamingCall[] newArray(int size) {
+ return new StreamingCall[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mComponentName, flags);
+ dest.writeString16NoHelper(mDisplayName);
+ dest.writeParcelable(mAddress, flags);
+ dest.writeBundle(mExtras);
+ dest.writeInt(mState);
+ }
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "STATE_" },
+ value = {
+ STATE_STREAMING,
+ STATE_HOLDING,
+ STATE_DISCONNECTED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StreamingCallState {}
+
+ private final ComponentName mComponentName;
+ private final String mDisplayName;
+ private final Uri mAddress;
+ private final Bundle mExtras;
+ @StreamingCallState
+ private int mState;
+ private StreamingCallAdapter mAdapter = null;
+
+ public StreamingCall(@NonNull ComponentName componentName, @NonNull String displayName,
+ @NonNull Uri address, @NonNull Bundle extras) {
+ mComponentName = componentName;
+ mDisplayName = displayName;
+ mAddress = address;
+ mExtras = extras;
+ mState = STATE_STREAMING;
+ }
+
+ /**
+ * @hide
+ */
+ public void setAdapter(StreamingCallAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * @return The {@link ComponentName} to identify the original voip app of this
+ * {@code StreamingCall}. General streaming sender app can use this to query necessary
+ * information (app icon etc.) in order to present notification of the streaming call on the
+ * receiver side.
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * @return The display name that the general streaming sender app can use this to present the
+ * {@code StreamingCall} to the receiver side.
+ */
+ @NonNull
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * @return The address (e.g., phone number) to which the {@code StreamingCall} is currently
+ * connected.
+ */
+ @NonNull
+ public Uri getAddress() {
+ return mAddress;
+ }
+
+ /**
+ * @return The state of this {@code StreamingCall}.
+ */
+ @StreamingCallState
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * @return The extra info the general streaming app need to stream the call from voip app or
+ * D2DI sdk.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Sets the state of this {@code StreamingCall}. The general streaming sender app can use this
+ * to request holding, unholding and disconnecting this {@code StreamingCall}.
+ * @param state The current streaming state of the call.
+ */
+ public void setStreamingState(@StreamingCallState int state) {
+ mAdapter.setStreamingState(state);
+ }
+}
diff --git a/telecomm/java/android/telecom/StreamingCallAdapter.java b/telecomm/java/android/telecom/StreamingCallAdapter.java
new file mode 100644
index 0000000..bd8727d
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCallAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.os.RemoteException;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Receives commands from {@link CallStreamingService} implementations which should be executed by
+ * Telecom. When Telecom binds to a {@link CallStreamingService}, an instance of this class is given
+ * to the general streaming app through which it can manipulate the streaming calls. Whe the general
+ * streaming app is notified of new ongoing streaming calls, it can execute
+ * {@link StreamingCall#setStreamingState(int)} for the ongoing streaming calls the user on the
+ * receiver side would like to hold, unhold and disconnect.
+ *
+ * @hide
+ */
+public final class StreamingCallAdapter {
+ private final IStreamingCallAdapter mAdapter;
+
+ /**
+ * {@hide}
+ */
+ public StreamingCallAdapter(IStreamingCallAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Instruct telecom to change the state of the streaming call.
+ *
+ * @param state The streaming state to set
+ */
+ public void setStreamingState(@StreamingCall.StreamingCallState int state) {
+ try {
+ mAdapter.setStreamingState(state);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 682dba1..16816ff 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -139,6 +139,7 @@
private static final String ON_ANSWER = "onAnswer";
private static final String ON_REJECT = "onReject";
private static final String ON_DISCONNECT = "onDisconnect";
+ private static final String ON_STREAMING_STARTED = "onStreamingStarted";
private void handleCallEventCallback(String action, String callId, int code,
ResultReceiver ackResultReceiver) {
@@ -174,6 +175,9 @@
case ON_ANSWER:
callback.onAnswer(code, outcomeReceiverWrapper);
break;
+ case ON_STREAMING_STARTED:
+ callback.onCallStreamingStarted(outcomeReceiverWrapper);
+ break;
}
});
} catch (Exception e) {
@@ -249,9 +253,7 @@
if (call != null) {
CallEventCallback callback = call.getCallEventCallback();
Executor executor = call.getExecutor();
- executor.execute(() -> {
- callback.onCallAudioStateChanged(callAudioState);
- });
+ executor.execute(() -> callback.onCallAudioStateChanged(callAudioState));
}
}
@@ -259,5 +261,23 @@
public void removeCallFromTransactionalServiceWrapper(String callId) {
untrackCall(callId);
}
+
+ @Override
+ public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) {
+ handleCallEventCallback(ON_STREAMING_STARTED, callId, 0, resultReceiver);
+ }
+
+ @Override
+ public void onCallStreamingFailed(String callId, int reason) {
+ Log.i(TAG, TextUtils.formatSimple("onCallAudioStateChanged: callId=[%s], reason=[%s]",
+ callId, reason));
+ // lookup the callEventCallback associated with the particular call
+ TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+ if (call != null) {
+ CallEventCallback callback = call.getCallEventCallback();
+ Executor executor = call.getExecutor();
+ executor.execute(() -> callback.onCallStreamingFailed(reason));
+ }
+ }
};
}
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index bf68c5e..dc0aeac 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -28,4 +28,5 @@
void setInactive(String callId, in ResultReceiver callback);
void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
void rejectCall(String callId, in ResultReceiver callback);
+ void startCallStreaming(String callId, in ResultReceiver callback);
}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
index 7f5825a..c45ef97 100644
--- a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -35,6 +35,9 @@
void onReject(String callId, in ResultReceiver callback);
void onDisconnect(String callId, in ResultReceiver callback);
void onCallAudioStateChanged(String callId, in CallAudioState callAudioState);
+ // Streaming related. Client registered call streaming capabilities should override
+ void onCallStreamingStarted(String callId, in ResultReceiver callback);
+ void onCallStreamingFailed(String callId, int reason);
// hidden methods that help with cleanup
void removeCallFromTransactionalServiceWrapper(String callId);
}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
new file mode 100644
index 0000000..6d53fd2
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+import android.telecom.StreamingCall;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Internal remote interface for call streaming services.
+ *
+ * @see android.telecom.CallStreamingService
+ *
+ * {@hide}
+ */
+oneway interface ICallStreamingService {
+ void setStreamingCallAdapter(in IStreamingCallAdapter streamingCallAdapter);
+ void onCallStreamingStarted(in StreamingCall call);
+ void onCallStreamingStopped();
+ void onCallStreamingStateChanged(int state);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
new file mode 100644
index 0000000..51424a6
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+/**
+ * Internal remote callback interface for call streaming services.
+ *
+ * @see android.telecom.StreamingCallAdapter
+ *
+ * {@hide}
+ */
+oneway interface IStreamingCallAdapter {
+ void setStreamingState(int state);
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
index efe242c..d3a5885 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
@@ -32,6 +32,7 @@
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
+import java.util.ArrayList;
public class MeshActivity extends Activity {
@Override
@@ -64,7 +65,9 @@
vertexBuffer.put(5, 400.0f);
vertexBuffer.rewind();
Mesh mesh = Mesh.make(
- meshSpec, Mesh.Mode.Triangles, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+ meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+
+ canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
int numTriangles = 100;
// number of triangles plus first 2 vertices
@@ -95,12 +98,10 @@
}
iVertexBuffer.rewind();
indexBuffer.rewind();
- Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.Mode.Triangles, iVertexBuffer, 102,
- indexBuffer, new Rect(0, 0, 1000, 1000));
-
+ Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer,
+ new Rect(0, 0, 1000, 1000));
Paint paint = new Paint();
paint.setColor(Color.RED);
- canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
canvas.drawMesh(mesh2, BlendMode.COLOR, paint);
}
@@ -114,10 +115,9 @@
+ " color = vec4(1.0, 0.0, 0.0, 1.0);"
+ " return varyings.position;\n"
+ "}";
- Attribute[] attList =
- new Attribute[] {new Attribute(MeshSpecification.FLOAT2, 0, "position")};
- Varying[] varyList =
- new MeshSpecification.Varying[] {};
+ ArrayList<Attribute> attList = new ArrayList<>();
+ attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position"));
+ ArrayList<Varying> varyList = new ArrayList<>();
return MeshSpecification.make(attList, 8, varyList, vs, fs);
}
}