Merge "For non-udfps, detectFP if primaryAuth required"
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index d281da0..a3390b7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -30,6 +30,25 @@
*/
interface IJobCallback {
/**
+ * Immediate callback to the system after sending a data transfer download progress request
+ * signal; used to quickly detect ANR.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param workId Unique integer used to identify a specific work item.
+ * @param transferredBytes How much data has been downloaded, in bytes.
+ */
+ void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+ long transferredBytes);
+ /**
+ * Immediate callback to the system after sending a data transfer upload progress request
+ * signal; used to quickly detect ANR.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param workId Unique integer used to identify a specific work item.
+ * @param transferredBytes How much data has been uploaded, in bytes.
+ */
+ void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId, long transferredBytes);
+ /**
* Immediate callback to the system after sending a start signal, used to quickly detect ANR.
*
* @param jobId Unique integer used to identify this job.
@@ -65,4 +84,24 @@
*/
@UnsupportedAppUsage
void jobFinished(int jobId, boolean reschedule);
+ /*
+ * Inform JobScheduler of a change in the estimated transfer payload.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param item The particular JobWorkItem this progress is associated with, if any.
+ * @param downloadBytes How many bytes the app expects to download.
+ * @param uploadBytes How many bytes the app expects to upload.
+ */
+ void updateEstimatedNetworkBytes(int jobId, in JobWorkItem item,
+ long downloadBytes, long uploadBytes);
+ /*
+ * Update JobScheduler of how much data the job has successfully transferred.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param item The particular JobWorkItem this progress is associated with, if any.
+ * @param transferredDownloadBytes The number of bytes that have successfully been downloaded.
+ * @param transferredUploadBytes The number of bytes that have successfully been uploaded.
+ */
+ void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
+ long transferredDownloadBytes, long transferredUploadBytes);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
index 22ad252..2bb82bd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
@@ -17,6 +17,7 @@
package android.app.job;
import android.app.job.JobParameters;
+import android.app.job.JobWorkItem;
/**
* Interface that the framework uses to communicate with application code that implements a
@@ -31,4 +32,8 @@
/** Stop execution of application's job. */
@UnsupportedAppUsage
void stopJob(in JobParameters jobParams);
+ /** Update JS of how much data has been downloaded. */
+ void getTransferredDownloadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
+ /** Update JS of how much data has been uploaded. */
+ void getTransferredUploadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index e0db3a6..76f71a2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -23,8 +23,11 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserIdInt;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.ClipData;
import android.content.Context;
+import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -94,6 +97,16 @@
*/
@SystemService(Context.JOB_SCHEDULER_SERVICE)
public abstract class JobScheduler {
+ /**
+ * Whether to throw an exception when an app doesn't properly implement all the necessary
+ * data transfer APIs.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION = 255371817L;
+
/** @hide */
@IntDef(prefix = { "RESULT_" }, value = {
RESULT_FAILURE,
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index d184d44..bad641c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -16,7 +16,13 @@
package android.app.job;
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
+import android.compat.Compatibility;
import android.content.Intent;
import android.os.IBinder;
@@ -72,6 +78,28 @@
public boolean onStopJob(JobParameters params) {
return JobService.this.onStopJob(params);
}
+
+ @Override
+ @BytesLong
+ public long getTransferredDownloadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (item == null) {
+ return JobService.this.getTransferredDownloadBytes();
+ } else {
+ return JobService.this.getTransferredDownloadBytes(item);
+ }
+ }
+
+ @Override
+ @BytesLong
+ public long getTransferredUploadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (item == null) {
+ return JobService.this.getTransferredUploadBytes();
+ } else {
+ return JobService.this.getTransferredUploadBytes(item);
+ }
+ }
};
}
return mEngine.getBinder();
@@ -171,4 +199,169 @@
* to end the job entirely. Regardless of the value returned, your job must stop executing.
*/
public abstract boolean onStopJob(JobParameters params);
+
+ /**
+ * Update how much data this job will transfer. This method can
+ * be called multiple times within the first 30 seconds after
+ * {@link #onStartJob(JobParameters)} has been called. Only
+ * one call will be heeded after that time has passed.
+ *
+ * This method (or an overload) must be called within the first
+ * 30 seconds for a data transfer job if a payload size estimate
+ * was not provided at the time of scheduling.
+ *
+ * @hide
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ */
+ public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+ @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+ mEngine.updateEstimatedNetworkBytes(params, null, downloadBytes, uploadBytes);
+ }
+
+ /**
+ * Update how much data will transfer for the JobWorkItem. This
+ * method can be called multiple times within the first 30 seconds
+ * after {@link #onStartJob(JobParameters)} has been called.
+ * Only one call will be heeded after that time has passed.
+ *
+ * This method (or an overload) must be called within the first
+ * 30 seconds for a data transfer job if a payload size estimate
+ * was not provided at the time of scheduling.
+ *
+ * @hide
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ */
+ public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem jobWorkItem,
+ @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+ mEngine.updateEstimatedNetworkBytes(params, jobWorkItem, downloadBytes, uploadBytes);
+ }
+
+ /**
+ * Tell JobScheduler how much data has successfully been transferred for the data transfer job.
+ * @hide
+ */
+ public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+ @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+ mEngine.updateTransferredNetworkBytes(params, null,
+ transferredDownloadBytes, transferredUploadBytes);
+ }
+
+ /**
+ * Tell JobScheduler how much data has been transferred for the data transfer
+ * {@link JobWorkItem}.
+ * @hide
+ */
+ public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item,
+ @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
+ mEngine.updateTransferredNetworkBytes(params, item,
+ transferredDownloadBytes, transferredUploadBytes);
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated download bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+ * hasn't been called recently.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @hide
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredDownloadBytes() {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated upload bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
+ * hasn't been called recently.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @hide
+ * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredUploadBytes() {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated download bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+ * hasn't been called recently and the job has
+ * {@link JobWorkItem JobWorkItems} that have been
+ * {@link JobParameters#dequeueWork dequeued} but not
+ * {@link JobParameters#completeWork(JobWorkItem) completed}.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @hide
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
+ if (item == null) {
+ return getTransferredDownloadBytes();
+ }
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
+ * will call this if the job has specified positive estimated upload bytes and
+ * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
+ * hasn't been called recently and the job has
+ * {@link JobWorkItem JobWorkItems} that have been
+ * {@link JobParameters#dequeueWork dequeued} but not
+ * {@link JobParameters#completeWork(JobWorkItem) completed}.
+ *
+ * <p>
+ * This must be implemented for all data transfer jobs.
+ *
+ * @hide
+ * @see JobInfo#NETWORK_BYTES_UNKNOWN
+ */
+ // TODO(255371817): specify the actual time JS will wait for progress before requesting
+ @BytesLong
+ public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
+ if (item == null) {
+ return getTransferredUploadBytes();
+ }
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ // Regular jobs don't have to implement this and JobScheduler won't call this API for
+ // non-data transfer jobs.
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 3d43d20..83296a6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -16,7 +16,13 @@
package android.app.job;
+import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
+import android.compat.Compatibility;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
@@ -25,6 +31,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.os.SomeArgs;
+
import java.lang.ref.WeakReference;
/**
@@ -51,6 +59,20 @@
* Message that the client has completed execution of this job.
*/
private static final int MSG_JOB_FINISHED = 2;
+ /**
+ * Message that will result in a call to
+ * {@link #getTransferredDownloadBytes(JobParameters, JobWorkItem)}.
+ */
+ private static final int MSG_GET_TRANSFERRED_DOWNLOAD_BYTES = 3;
+ /**
+ * Message that will result in a call to
+ * {@link #getTransferredUploadBytes(JobParameters, JobWorkItem)}.
+ */
+ private static final int MSG_GET_TRANSFERRED_UPLOAD_BYTES = 4;
+ /** Message that the client wants to update JobScheduler of the data transfer progress. */
+ private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
+ /** Message that the client wants to update JobScheduler of the estimated transfer size. */
+ private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
private final IJobService mBinder;
@@ -68,6 +90,32 @@
}
@Override
+ public void getTransferredDownloadBytes(@NonNull JobParameters jobParams,
+ @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+ JobServiceEngine service = mService.get();
+ if (service != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = jobParams;
+ args.arg2 = jobWorkItem;
+ service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_DOWNLOAD_BYTES, args)
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void getTransferredUploadBytes(@NonNull JobParameters jobParams,
+ @Nullable JobWorkItem jobWorkItem) throws RemoteException {
+ JobServiceEngine service = mService.get();
+ if (service != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = jobParams;
+ args.arg2 = jobWorkItem;
+ service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_UPLOAD_BYTES, args)
+ .sendToTarget();
+ }
+ }
+
+ @Override
public void startJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
@@ -98,9 +146,9 @@
@Override
public void handleMessage(Message msg) {
- final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
- case MSG_EXECUTE_JOB:
+ case MSG_EXECUTE_JOB: {
+ final JobParameters params = (JobParameters) msg.obj;
try {
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
ackStartMessage(params, workOngoing);
@@ -109,7 +157,9 @@
throw new RuntimeException(e);
}
break;
- case MSG_STOP_JOB:
+ }
+ case MSG_STOP_JOB: {
+ final JobParameters params = (JobParameters) msg.obj;
try {
boolean ret = JobServiceEngine.this.onStopJob(params);
ackStopMessage(params, ret);
@@ -118,7 +168,9 @@
throw new RuntimeException(e);
}
break;
- case MSG_JOB_FINISHED:
+ }
+ case MSG_JOB_FINISHED: {
+ final JobParameters params = (JobParameters) msg.obj;
final boolean needsReschedule = (msg.arg2 == 1);
IJobCallback callback = params.getCallback();
if (callback != null) {
@@ -132,19 +184,117 @@
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
+ }
+ case MSG_GET_TRANSFERRED_DOWNLOAD_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ final JobWorkItem item = (JobWorkItem) args.arg2;
+ try {
+ long ret = JobServiceEngine.this.getTransferredDownloadBytes(params, item);
+ ackGetTransferredDownloadBytesMessage(params, item, ret);
+ } catch (Exception e) {
+ Log.e(TAG, "Application unable to handle getTransferredDownloadBytes.", e);
+ throw new RuntimeException(e);
+ }
+ args.recycle();
+ break;
+ }
+ case MSG_GET_TRANSFERRED_UPLOAD_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ final JobWorkItem item = (JobWorkItem) args.arg2;
+ try {
+ long ret = JobServiceEngine.this.getTransferredUploadBytes(params, item);
+ ackGetTransferredUploadBytesMessage(params, item, ret);
+ } catch (Exception e) {
+ Log.e(TAG, "Application unable to handle getTransferredUploadBytes.", e);
+ throw new RuntimeException(e);
+ }
+ args.recycle();
+ break;
+ }
+ case MSG_UPDATE_TRANSFERRED_NETWORK_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ IJobCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.updateTransferredNetworkBytes(params.getJobId(),
+ (JobWorkItem) args.arg2, args.argl1, args.argl2);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating data transfer progress to system:"
+ + " binder has gone away.");
+ }
+ } else {
+ Log.e(TAG, "updateDataTransferProgress() called for a nonexistent job id.");
+ }
+ args.recycle();
+ break;
+ }
+ case MSG_UPDATE_ESTIMATED_NETWORK_BYTES: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ IJobCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.updateEstimatedNetworkBytes(params.getJobId(),
+ (JobWorkItem) args.arg2, args.argl1, args.argl2);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error updating estimated transfer size to system:"
+ + " binder has gone away.");
+ }
+ } else {
+ Log.e(TAG,
+ "updateEstimatedNetworkBytes() called for a nonexistent job id.");
+ }
+ args.recycle();
+ break;
+ }
default:
Log.e(TAG, "Unrecognised message received.");
break;
}
}
+ private void ackGetTransferredDownloadBytesMessage(@NonNull JobParameters params,
+ @Nullable JobWorkItem item, long progress) {
+ final IJobCallback callback = params.getCallback();
+ final int jobId = params.getJobId();
+ final int workId = item == null ? -1 : item.getWorkId();
+ if (callback != null) {
+ try {
+ callback.acknowledgeGetTransferredDownloadBytesMessage(jobId, workId, progress);
+ } catch (RemoteException e) {
+ Log.e(TAG, "System unreachable for returning progress.");
+ }
+ } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Attempting to ack a job that has already been processed.");
+ }
+ }
+
+ private void ackGetTransferredUploadBytesMessage(@NonNull JobParameters params,
+ @Nullable JobWorkItem item, long progress) {
+ final IJobCallback callback = params.getCallback();
+ final int jobId = params.getJobId();
+ final int workId = item == null ? -1 : item.getWorkId();
+ if (callback != null) {
+ try {
+ callback.acknowledgeGetTransferredUploadBytesMessage(jobId, workId, progress);
+ } catch (RemoteException e) {
+ Log.e(TAG, "System unreachable for returning progress.");
+ }
+ } else if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Attempting to ack a job that has already been processed.");
+ }
+ }
+
private void ackStartMessage(JobParameters params, boolean workOngoing) {
final IJobCallback callback = params.getCallback();
final int jobId = params.getJobId();
if (callback != null) {
try {
callback.acknowledgeStartMessage(jobId, workOngoing);
- } catch(RemoteException e) {
+ } catch (RemoteException e) {
Log.e(TAG, "System unreachable for starting job.");
}
} else {
@@ -213,4 +363,73 @@
m.arg2 = needsReschedule ? 1 : 0;
m.sendToTarget();
}
+
+ /**
+ * Engine's request to get how much data has been downloaded.
+ *
+ * @hide
+ * @see JobService#getTransferredDownloadBytes()
+ */
+ @BytesLong
+ public long getTransferredDownloadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Engine's request to get how much data has been uploaded.
+ *
+ * @hide
+ * @see JobService#getTransferredUploadBytes()
+ */
+ @BytesLong
+ public long getTransferredUploadBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item) {
+ if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+ return 0;
+ }
+
+ /**
+ * Call in to engine to report data transfer progress.
+ *
+ * @hide
+ * @see JobService#updateTransferredNetworkBytes(JobParameters, long, long)
+ */
+ public void updateTransferredNetworkBytes(@NonNull JobParameters params,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ if (params == null) {
+ throw new NullPointerException("params");
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = params;
+ args.arg2 = item;
+ args.argl1 = downloadBytes;
+ args.argl2 = uploadBytes;
+ mHandler.obtainMessage(MSG_UPDATE_TRANSFERRED_NETWORK_BYTES, args).sendToTarget();
+ }
+
+ /**
+ * Call in to engine to report data transfer progress.
+ *
+ * @hide
+ * @see JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)
+ */
+ public void updateEstimatedNetworkBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item,
+ @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
+ if (params == null) {
+ throw new NullPointerException("params");
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = params;
+ args.arg2 = item;
+ args.argl1 = downloadBytes;
+ args.argl2 = uploadBytes;
+ mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
+ }
}
\ No newline at end of file
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 334647e..9aa6b1c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -21,6 +21,7 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.IJobCallback;
@@ -187,6 +188,18 @@
public long mStoppedTime;
@Override
+ public void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
+ @BytesLong long transferredBytes) {
+ doAcknowledgeGetTransferredDownloadBytesMessage(this, jobId, workId, transferredBytes);
+ }
+
+ @Override
+ public void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId,
+ @BytesLong long transferredBytes) {
+ doAcknowledgeGetTransferredUploadBytesMessage(this, jobId, workId, transferredBytes);
+ }
+
+ @Override
public void acknowledgeStartMessage(int jobId, boolean ongoing) {
doAcknowledgeStartMessage(this, jobId, ongoing);
}
@@ -210,6 +223,18 @@
public void jobFinished(int jobId, boolean reschedule) {
doJobFinished(this, jobId, reschedule);
}
+
+ @Override
+ public void updateEstimatedNetworkBytes(int jobId, JobWorkItem item,
+ long downloadBytes, long uploadBytes) {
+ doUpdateEstimatedNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+ }
+
+ @Override
+ public void updateTransferredNetworkBytes(int jobId, JobWorkItem item,
+ long downloadBytes, long uploadBytes) {
+ doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
+ }
}
JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
@@ -506,6 +531,16 @@
}
}
+ private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback jobCallback, int jobId,
+ int workId, @BytesLong long transferredBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
+ private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback jobCallback, int jobId,
+ int workId, @BytesLong long transferredBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
doCallback(cb, reschedule, null);
}
@@ -558,6 +593,16 @@
}
}
+ private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
+ private void doUpdateEstimatedNetworkBytes(JobCallback jobCallback, int jobId,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ }
+
/**
* We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
* we intend to send to the client - we stop sending work when the service is unbound so until
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 c2602f2..145ac52 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -412,6 +412,14 @@
/** Version of the db schema. */
private static final int JOBS_FILE_VERSION = 1;
+ /**
+ * For legacy reasons, this tag is used to encapsulate the entire job list.
+ */
+ private static final String XML_TAG_JOB_INFO = "job-info";
+ /**
+ * For legacy reasons, this tag represents a single {@link JobStatus} object.
+ */
+ private static final String XML_TAG_JOB = "job";
/** Tag corresponds to constraints this job needs. */
private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
/** Tag corresponds to execution parameters. */
@@ -645,19 +653,19 @@
out.startDocument(null, true);
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- out.startTag(null, "job-info");
+ out.startTag(null, XML_TAG_JOB_INFO);
out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
for (int i=0; i<jobList.size(); i++) {
JobStatus jobStatus = jobList.get(i);
if (DEBUG) {
Slog.d(TAG, "Saving job " + jobStatus.getJobId());
}
- out.startTag(null, "job");
+ out.startTag(null, XML_TAG_JOB);
addAttributesToJobTag(out, jobStatus);
writeConstraintsToXml(out, jobStatus);
writeExecutionCriteriaToXml(out, jobStatus);
writeBundleToXml(jobStatus.getJob().getExtras(), out);
- out.endTag(null, "job");
+ out.endTag(null, XML_TAG_JOB);
numJobs++;
if (jobStatus.getUid() == Process.SYSTEM_UID) {
@@ -667,7 +675,7 @@
}
}
}
- out.endTag(null, "job-info");
+ out.endTag(null, XML_TAG_JOB_INFO);
out.endDocument();
file.finishWrite(fos);
@@ -903,17 +911,17 @@
return;
}
boolean needFileMigration = false;
- long now = sElapsedRealtimeClock.millis();
+ long nowElapsed = sElapsedRealtimeClock.millis();
for (File file : files) {
final AtomicFile aFile = createJobFile(file);
try (FileInputStream fis = aFile.openRead()) {
synchronized (mLock) {
- jobs = readJobMapImpl(fis, rtcGood);
+ jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
if (jobs != null) {
for (int i = 0; i < jobs.size(); i++) {
JobStatus js = jobs.get(i);
js.prepareLocked();
- js.enqueueTime = now;
+ js.enqueueTime = nowElapsed;
this.jobSet.add(js);
numJobs++;
@@ -959,7 +967,7 @@
}
}
- private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood)
+ private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood, long nowElapsed)
throws XmlPullParserException, IOException {
TypedXmlPullParser parser = Xml.resolvePullParser(fis);
@@ -977,28 +985,24 @@
}
String tagName = parser.getName();
- if ("job-info".equals(tagName)) {
+ if (XML_TAG_JOB_INFO.equals(tagName)) {
final List<JobStatus> jobs = new ArrayList<JobStatus>();
- final int version;
+ final int version = parser.getAttributeInt(null, "version");
// Read in version info.
- try {
- version = Integer.parseInt(parser.getAttributeValue(null, "version"));
- if (version > JOBS_FILE_VERSION || version < 0) {
- Slog.d(TAG, "Invalid version number, aborting jobs file read.");
- return null;
- }
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Invalid version number, aborting jobs file read.");
+ if (version > JOBS_FILE_VERSION || version < 0) {
+ Slog.d(TAG, "Invalid version number, aborting jobs file read.");
return null;
}
+
eventType = parser.next();
do {
// Read each <job/>
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
// Start reading job.
- if ("job".equals(tagName)) {
- JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser, version);
+ if (XML_TAG_JOB.equals(tagName)) {
+ JobStatus persistedJob =
+ restoreJobFromXml(rtcIsGood, parser, version, nowElapsed);
if (persistedJob != null) {
if (DEBUG) {
Slog.d(TAG, "Read out " + persistedJob);
@@ -1022,7 +1026,7 @@
* @return Newly instantiated job holding all the information we just read out of the xml tag.
*/
private JobStatus restoreJobFromXml(boolean rtcIsGood, TypedXmlPullParser parser,
- int schemaVersion) throws XmlPullParserException, IOException {
+ int schemaVersion, long nowElapsed) throws XmlPullParserException, IOException {
JobInfo.Builder jobBuilder;
int uid, sourceUserId;
long lastSuccessfulRunTime;
@@ -1113,18 +1117,9 @@
}
// Tuple of (earliest runtime, latest runtime) in UTC.
- final Pair<Long, Long> rtcRuntimes;
- try {
- rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
- } catch (NumberFormatException e) {
- if (DEBUG) {
- Slog.d(TAG, "Error parsing execution time parameters, skipping.");
- }
- return null;
- }
+ final Pair<Long, Long> rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
- final long elapsedNow = sElapsedRealtimeClock.millis();
- Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
+ Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, nowElapsed);
if (XML_TAG_PERIODIC.equals(parser.getName())) {
try {
@@ -1137,8 +1132,8 @@
// from now. This is the latest the periodic could be pushed out. This could
// happen if the periodic ran early (at flex time before period), and then the
// device rebooted.
- if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
- final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
+ if (elapsedRuntimes.second > nowElapsed + periodMillis + flexMillis) {
+ final long clampedLateRuntimeElapsed = nowElapsed + flexMillis
+ periodMillis;
final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
- flexMillis;
@@ -1163,11 +1158,11 @@
} else if (XML_TAG_ONEOFF.equals(parser.getName())) {
try {
if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
- jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
+ jobBuilder.setMinimumLatency(elapsedRuntimes.first - nowElapsed);
}
if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
jobBuilder.setOverrideDeadline(
- elapsedRuntimes.second - elapsedNow);
+ elapsedRuntimes.second - nowElapsed);
}
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading job execution criteria, skipping.");
@@ -1236,7 +1231,7 @@
// And now we're done
final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
- sourceUserId, elapsedNow);
+ sourceUserId, nowElapsed);
JobStatus js = new JobStatus(
builtJob, uid, sourcePackageName, sourceUserId,
appBucket, sourceTag,
@@ -1246,9 +1241,10 @@
return js;
}
- private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
+ private JobInfo.Builder buildBuilderFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException {
// Pull out required fields from <job> attributes.
- int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
+ int jobId = parser.getAttributeInt(null, "jobid");
String packageName = parser.getAttributeValue(null, "package");
String className = parser.getAttributeValue(null, "class");
ComponentName cname = new ComponentName(packageName, className);
@@ -1405,20 +1401,13 @@
* @return A Pair of timestamps in UTC wall-clock time. The first is the earliest
* time at which the job is to become runnable, and the second is the deadline at
* which it becomes overdue to execute.
- * @throws NumberFormatException
*/
- private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
- throws NumberFormatException {
- String val;
+ private Pair<Long, Long> buildRtcExecutionTimesFromXml(TypedXmlPullParser parser) {
// Pull out execution time data.
- val = parser.getAttributeValue(null, "delay");
- final long earliestRunTimeRtc = (val != null)
- ? Long.parseLong(val)
- : JobStatus.NO_EARLIEST_RUNTIME;
- val = parser.getAttributeValue(null, "deadline");
- final long latestRunTimeRtc = (val != null)
- ? Long.parseLong(val)
- : JobStatus.NO_LATEST_RUNTIME;
+ final long earliestRunTimeRtc =
+ parser.getAttributeLong(null, "delay", JobStatus.NO_EARLIEST_RUNTIME);
+ final long latestRunTimeRtc =
+ parser.getAttributeLong(null, "deadline", JobStatus.NO_LATEST_RUNTIME);
return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
}
}
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
index 20aa7d3..a8aa033 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -38,9 +38,10 @@
constexpr const mode_t kIdmapFilePermission = S_IRUSR | S_IWUSR; // u=rw-, g=---, o=---
extern "C" bool
-CreateFrroFile(std::string& out_err_result, std::string& packageName, std::string& overlayName,
- std::string& targetPackageName, std::optional<std::string>& targetOverlayable,
- std::vector<FabricatedOverlayEntryParameters>& entries_params,
+CreateFrroFile(std::string& out_err_result, const std::string& packageName,
+ const std::string& overlayName, const std::string& targetPackageName,
+ const std::optional<std::string>& targetOverlayable,
+ const std::vector<FabricatedOverlayEntryParameters>& entries_params,
const std::string& frro_file_path) {
android::idmap2::FabricatedOverlay::Builder builder(packageName, overlayName,
targetPackageName);
@@ -90,9 +91,46 @@
return true;
}
+static PolicyBitmask GetFulfilledPolicy(const bool isSystem, const bool isVendor,
+ const bool isProduct, const bool isTargetSignature,
+ const bool isOdm, const bool isOem) {
+ auto fulfilled_policy = static_cast<PolicyBitmask>(PolicyFlags::PUBLIC);
+
+ if (isSystem) {
+ fulfilled_policy |= PolicyFlags::SYSTEM_PARTITION;
+ }
+ if (isVendor) {
+ fulfilled_policy |= PolicyFlags::VENDOR_PARTITION;
+ }
+ if (isProduct) {
+ fulfilled_policy |= PolicyFlags::PRODUCT_PARTITION;
+ }
+ if (isOdm) {
+ fulfilled_policy |= PolicyFlags::ODM_PARTITION;
+ }
+ if (isOem) {
+ fulfilled_policy |= PolicyFlags::OEM_PARTITION;
+ }
+ if (isTargetSignature) {
+ fulfilled_policy |= PolicyFlags::SIGNATURE;
+ }
+
+ // Not support actor_signature and config_overlay_signature
+ fulfilled_policy &=
+ ~(PolicyFlags::ACTOR_SIGNATURE | PolicyFlags::CONFIG_SIGNATURE);
+
+ ALOGV(
+ "fulfilled_policy = 0x%08x, isSystem = %d, isVendor = %d, isProduct = %d,"
+ " isTargetSignature = %d, isOdm = %d, isOem = %d,",
+ fulfilled_policy, isSystem, isVendor, isProduct, isTargetSignature, isOdm, isOem);
+ return fulfilled_policy;
+}
+
extern "C" bool
CreateIdmapFile(std::string& out_err, const std::string& targetPath, const std::string& overlayPath,
- const std::string& idmapPath, const std::string& overlayName) {
+ const std::string& idmapPath, const std::string& overlayName,
+ const bool isSystem, const bool isVendor, const bool isProduct,
+ const bool isTargetSignature, const bool isOdm, const bool isOem) {
// idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap
// guarantees that existing memory maps will continue to be valid and unaffected. The file must
// be deleted before attempting to create the idmap, so that if idmap creation fails, the
@@ -114,14 +152,11 @@
}
// Overlay self target process. Only allow self-targeting types.
- const auto fulfilled_policies = static_cast<PolicyBitmask>(
- PolicyFlags::PUBLIC | PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION |
- PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE | PolicyFlags::ODM_PARTITION |
- PolicyFlags::OEM_PARTITION | PolicyFlags::ACTOR_SIGNATURE |
- PolicyFlags::CONFIG_SIGNATURE);
+ const auto fulfilled_policies = GetFulfilledPolicy(isSystem, isVendor, isProduct,
+ isTargetSignature, isOdm, isOem);
const auto idmap = Idmap::FromContainers(**target, **overlay, overlayName,
- fulfilled_policies, false /* enforce_overlayable */);
+ fulfilled_policies, true /* enforce_overlayable */);
if (!idmap) {
out_err = base::StringPrintf("Failed to create idmap because of %s",
idmap.GetErrorMessage().c_str());
diff --git a/core/api/current.txt b/core/api/current.txt
index fc319ce..dab32d2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12982,6 +12982,14 @@
package android.credentials {
+ public final class ClearCredentialStateRequest implements android.os.Parcelable {
+ ctor public ClearCredentialStateRequest(@NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public android.os.Bundle getData();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ClearCredentialStateRequest> CREATOR;
+ }
+
public final class CreateCredentialRequest implements android.os.Parcelable {
ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle);
method public int describeContents();
@@ -13009,8 +13017,9 @@
}
public final class CredentialManager {
- method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
- method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
+ method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.CredentialManagerException>);
+ method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CredentialManagerException>);
+ method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.CredentialManagerException>);
}
public class CredentialManagerException extends java.lang.Exception {
@@ -39319,6 +39328,149 @@
}
+package android.service.credentials {
+
+ public final class Action implements android.os.Parcelable {
+ ctor public Action(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+ method public int describeContents();
+ method @NonNull public android.app.PendingIntent getPendingIntent();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.Action> CREATOR;
+ }
+
+ public final class BeginCreateCredentialRequest implements android.os.Parcelable {
+ ctor public BeginCreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public String getCallingPackage();
+ method @NonNull public android.os.Bundle getData();
+ method @NonNull public String getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialRequest> CREATOR;
+ }
+
+ public final class BeginCreateCredentialResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.service.credentials.CreateEntry> getCreateEntries();
+ method @Nullable public android.service.credentials.CreateEntry getRemoteCreateEntry();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginCreateCredentialResponse> CREATOR;
+ }
+
+ public static final class BeginCreateCredentialResponse.Builder {
+ ctor public BeginCreateCredentialResponse.Builder();
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
+ method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.CreateEntry);
+ }
+
+ public final class CreateCredentialRequest implements android.os.Parcelable {
+ ctor public CreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public String getCallingPackage();
+ method @NonNull public android.os.Bundle getData();
+ method @NonNull public String getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateCredentialRequest> CREATOR;
+ }
+
+ public final class CreateEntry implements android.os.Parcelable {
+ ctor public CreateEntry(@NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+ method public int describeContents();
+ method @NonNull public android.app.PendingIntent getPendingIntent();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CreateEntry> CREATOR;
+ }
+
+ public final class CredentialEntry implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.credentials.Credential getCredential();
+ method @Nullable public android.app.PendingIntent getPendingIntent();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method @NonNull public String getType();
+ method public boolean isAutoSelectAllowed();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialEntry> CREATOR;
+ }
+
+ public static final class CredentialEntry.Builder {
+ ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
+ ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.credentials.Credential);
+ method @NonNull public android.service.credentials.CredentialEntry build();
+ method @NonNull public android.service.credentials.CredentialEntry.Builder setAutoSelectAllowed(@NonNull boolean);
+ }
+
+ public class CredentialProviderException extends java.lang.Exception {
+ ctor public CredentialProviderException(int, @NonNull String, @NonNull Throwable);
+ ctor public CredentialProviderException(int, @NonNull String);
+ ctor public CredentialProviderException(int, @NonNull Throwable);
+ ctor public CredentialProviderException(int);
+ method public int getErrorCode();
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract class CredentialProviderService extends android.app.Service {
+ ctor public CredentialProviderService();
+ method public abstract void onBeginCreateCredential(@NonNull android.service.credentials.BeginCreateCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginCreateCredentialResponse,android.service.credentials.CredentialProviderException>);
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onGetCredentials(@NonNull android.service.credentials.GetCredentialsRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.GetCredentialsResponse,android.service.credentials.CredentialProviderException>);
+ field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+ field public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
+ field public static final String EXTRA_CREATE_CREDENTIAL_RESULT = "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
+ field public static final String EXTRA_CREDENTIAL_RESULT = "android.service.credentials.extra.CREDENTIAL_RESULT";
+ field public static final String EXTRA_ERROR = "android.service.credentials.extra.ERROR";
+ field public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT = "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+ field public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService";
+ }
+
+ public final class CredentialsResponseContent implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.service.credentials.Action> getActions();
+ method @NonNull public java.util.List<android.service.credentials.CredentialEntry> getCredentialEntries();
+ method @Nullable public android.service.credentials.CredentialEntry getRemoteCredentialEntry();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CredentialsResponseContent> CREATOR;
+ }
+
+ public static final class CredentialsResponseContent.Builder {
+ ctor public CredentialsResponseContent.Builder();
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addAction(@NonNull android.service.credentials.Action);
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder addCredentialEntry(@NonNull android.service.credentials.CredentialEntry);
+ method @NonNull public android.service.credentials.CredentialsResponseContent build();
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
+ method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.CredentialEntry);
+ }
+
+ public final class GetCredentialsRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getCallingPackage();
+ method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsRequest> CREATOR;
+ }
+
+ public static final class GetCredentialsRequest.Builder {
+ ctor public GetCredentialsRequest.Builder(@NonNull String);
+ method @NonNull public android.service.credentials.GetCredentialsRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+ method @NonNull public android.service.credentials.GetCredentialsRequest build();
+ method @NonNull public android.service.credentials.GetCredentialsRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
+ }
+
+ public final class GetCredentialsResponse implements android.os.Parcelable {
+ method @NonNull public static android.service.credentials.GetCredentialsResponse createWithAuthentication(@NonNull android.service.credentials.Action);
+ method @NonNull public static android.service.credentials.GetCredentialsResponse createWithResponseContent(@NonNull android.service.credentials.CredentialsResponseContent);
+ method public int describeContents();
+ method @Nullable public android.service.credentials.Action getAuthenticationAction();
+ method @Nullable public android.service.credentials.CredentialsResponseContent getCredentialsResponseContent();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsResponse> CREATOR;
+ }
+
+}
+
package android.service.dreams {
public class DreamService extends android.app.Service implements android.view.Window.Callback {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eac990d..af18827 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7269,6 +7269,7 @@
method @NonNull public java.util.List<android.media.tv.tuner.frontend.FrontendStatusReadiness> getFrontendStatusReadiness(@NonNull int[]);
method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
+ method public boolean isLnaSupported();
method public boolean isLowestPriority(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
@@ -12818,14 +12819,14 @@
method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
}
- @Deprecated public final class CallAttributes implements android.os.Parcelable {
- ctor @Deprecated public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
- method @Deprecated public int describeContents();
- method @Deprecated @NonNull public android.telephony.CallQuality getCallQuality();
- method @Deprecated public int getNetworkType();
- method @Deprecated @NonNull public android.telephony.PreciseCallState getPreciseCallState();
- method @Deprecated public void writeToParcel(android.os.Parcel, int);
- field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
+ public final class CallAttributes implements android.os.Parcelable {
+ ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
+ method public int describeContents();
+ method @NonNull public android.telephony.CallQuality getCallQuality();
+ method public int getNetworkType();
+ method @NonNull public android.telephony.PreciseCallState getPreciseCallState();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
}
public final class CallForwardingInfo implements android.os.Parcelable {
@@ -12905,28 +12906,6 @@
method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int);
}
- public final class CallState implements android.os.Parcelable {
- method public int describeContents();
- method @Nullable public android.telephony.CallQuality getCallQuality();
- method public int getCallState();
- method public int getImsCallServiceType();
- method @Nullable public String getImsCallSessionId();
- method public int getImsCallType();
- method public int getNetworkType();
- method public void writeToParcel(@Nullable android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallState> CREATOR;
- }
-
- public static final class CallState.Builder {
- ctor public CallState.Builder(int);
- method @NonNull public android.telephony.CallState build();
- method @NonNull public android.telephony.CallState.Builder setCallQuality(@Nullable android.telephony.CallQuality);
- method @NonNull public android.telephony.CallState.Builder setImsCallServiceType(int);
- method @NonNull public android.telephony.CallState.Builder setImsCallSessionId(@Nullable String);
- method @NonNull public android.telephony.CallState.Builder setImsCallType(int);
- method @NonNull public android.telephony.CallState.Builder setNetworkType(int);
- }
-
public class CarrierConfigManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -13677,8 +13656,7 @@
}
public static interface TelephonyCallback.CallAttributesListener {
- method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
+ method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
}
public static interface TelephonyCallback.DataEnabledListener {
@@ -14779,7 +14757,6 @@
field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3
field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0
field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1
- field public static final int CALL_TYPE_NONE = 0; // 0x0
field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3
field public static final int CALL_TYPE_VOICE = 2; // 0x2
field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1
@@ -16144,6 +16121,12 @@
public abstract class AccessibilityDisplayProxy {
ctor public AccessibilityDisplayProxy(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
method public int getDisplayId();
+ method @NonNull public final java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAndEnabledServices();
+ method @NonNull public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
+ method public void interrupt();
+ method public void onAccessibilityEvent(@NonNull android.view.accessibility.AccessibilityEvent);
+ method public void onProxyConnected();
+ method public void setInstalledAndEnabledServices(@NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
}
public final class AccessibilityManager {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a61ade0..884870b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -47,6 +47,8 @@
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
import android.app.servertransaction.ActivityRelaunchItem;
@@ -799,7 +801,7 @@
ApplicationInfo appInfo;
int backupMode;
int userId;
- int operationType;
+ @BackupDestination int backupDestination;
public String toString() {
return "CreateBackupAgentData{appInfo=" + appInfo
+ " backupAgent=" + appInfo.backupAgentName
@@ -1034,12 +1036,12 @@
}
public final void scheduleCreateBackupAgent(ApplicationInfo app,
- int backupMode, int userId, int operationType) {
+ int backupMode, int userId, @BackupDestination int backupDestination) {
CreateBackupAgentData d = new CreateBackupAgentData();
d.appInfo = app;
d.backupMode = backupMode;
d.userId = userId;
- d.operationType = operationType;
+ d.backupDestination = backupDestination;
sendMessage(H.CREATE_BACKUP_AGENT, d);
}
@@ -4402,7 +4404,8 @@
context.setOuterContext(agent);
agent.attach(context);
- agent.onCreate(UserHandle.of(data.userId), data.operationType);
+ agent.onCreate(UserHandle.of(data.userId), data.backupDestination,
+ getOperationTypeFromBackupMode(data.backupMode));
binder = agent.onBind();
backupAgents.put(packageName, agent);
} catch (Exception e) {
@@ -4430,6 +4433,22 @@
}
}
+ @OperationType
+ private static int getOperationTypeFromBackupMode(int backupMode) {
+ switch (backupMode) {
+ case ApplicationThreadConstants.BACKUP_MODE_RESTORE:
+ case ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL:
+ return OperationType.RESTORE;
+ case ApplicationThreadConstants.BACKUP_MODE_FULL:
+ case ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL:
+ return OperationType.BACKUP;
+ default:
+ Slog.w(TAG, "Invalid backup mode when initialising BackupAgent: "
+ + backupMode);
+ return OperationType.UNKNOWN;
+ }
+ }
+
private String getBackupAgentName(CreateBackupAgentData data) {
String agentName = data.appInfo.backupAgentName;
// full backup operation but no app-supplied agent? use the default implementation
@@ -6407,23 +6426,28 @@
}
private void handleTrimMemory(int level) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory: " + level);
+ }
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
- if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- PropertyInvalidatedCache.onTrimMemory();
- }
+ try {
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+ PropertyInvalidatedCache.onTrimMemory();
+ }
- final ArrayList<ComponentCallbacks2> callbacks =
- collectComponentCallbacks(true /* includeUiContexts */);
+ final ArrayList<ComponentCallbacks2> callbacks =
+ collectComponentCallbacks(true /* includeUiContexts */);
- final int N = callbacks.size();
- for (int i = 0; i < N; i++) {
- callbacks.get(i).onTrimMemory(level);
+ final int N = callbacks.size();
+ for (int i = 0; i < N; i++) {
+ callbacks.get(i).onTrimMemory(level);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
WindowManagerGlobal.getInstance().trimMemory(level);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
unscheduleGcIdler();
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 818bdc2..9bf8550 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -54,6 +54,7 @@
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
+import android.healthconnect.HealthConnectManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArraySet;
@@ -66,8 +67,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Optional;
+import java.util.Set;
/**
* This class enforces the policies around the foreground service types.
@@ -655,11 +658,12 @@
*
* For test only.
*/
- public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest() {
+ public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest(
+ @NonNull Context context) {
if (mAllOfPermissions == null) {
return Optional.empty();
}
- return Optional.of(mAllOfPermissions.toStringArray());
+ return Optional.of(mAllOfPermissions.toStringArray(context));
}
/**
@@ -668,11 +672,12 @@
*
* For test only.
*/
- public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest() {
+ public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest(
+ @NonNull Context context) {
if (mAnyOfPermissions == null) {
return Optional.empty();
}
- return Optional.of(mAnyOfPermissions.toStringArray());
+ return Optional.of(mAnyOfPermissions.toStringArray(context));
}
/**
@@ -808,12 +813,12 @@
return sb.toString();
}
- @NonNull String[] toStringArray() {
- final String[] names = new String[mPermissions.length];
+ @NonNull String[] toStringArray(Context context) {
+ final ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < mPermissions.length; i++) {
- names[i] = mPermissions[i].mName;
+ mPermissions[i].addToList(context, list);
}
- return names;
+ return list.toArray(new String[list.size()]);
}
}
@@ -826,7 +831,7 @@
/**
* The name of this permission.
*/
- final @NonNull String mName;
+ protected final @NonNull String mName;
/**
* Constructor.
@@ -846,6 +851,10 @@
public String toString() {
return mName;
}
+
+ void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+ list.add(mName);
+ }
}
/**
@@ -859,15 +868,23 @@
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
+ return checkPermission(context, mName, callerUid, callerPid, packageName,
+ allowWhileInUse);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
+ int callerPid, String packageName, boolean allowWhileInUse) {
// Simple case, check if it's already granted.
- if (context.checkPermission(mName, callerPid, callerUid) == PERMISSION_GRANTED) {
+ if (context.checkPermission(name, callerPid, callerUid) == PERMISSION_GRANTED) {
return PERMISSION_GRANTED;
}
if (allowWhileInUse) {
// Check its appops
- final int opCode = AppOpsManager.permissionToOpCode(mName);
+ final int opCode = AppOpsManager.permissionToOpCode(name);
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
if (opCode != AppOpsManager.OP_NONE) {
final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
@@ -895,7 +912,7 @@
@Override
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
final int mode = appOpsManager.unsafeCheckOpRawNoThrow(mOpCode, callerUid, packageName);
@@ -915,7 +932,7 @@
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
final UsbManager usbManager = context.getSystemService(UsbManager.class);
final HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
@@ -941,7 +958,7 @@
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
@PackageManager.PermissionResult
- public int checkPermission(Context context, int callerUid, int callerPid,
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
String packageName, boolean allowWhileInUse) {
final UsbManager usbManager = context.getSystemService(UsbManager.class);
final UsbAccessory[] accessories = usbManager.getAccessoryList();
@@ -956,6 +973,45 @@
}
}
+ static class HealthConnectPermission extends RegularPermission {
+ private @Nullable String[] mPermissionNames;
+
+ HealthConnectPermission() {
+ super("Health Connect");
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final String[] perms = getPermissions(context);
+ for (String perm : perms) {
+ if (checkPermission(context, perm, callerUid, callerPid,
+ packageName, allowWhileInUse) == PERMISSION_GRANTED) {
+ return PERMISSION_GRANTED;
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+
+ @Override
+ void addToList(@NonNull Context context, @NonNull ArrayList<String> list) {
+ final String[] perms = getPermissions(context);
+ for (String perm : perms) {
+ list.add(perm);
+ }
+ }
+
+ private @NonNull String[] getPermissions(@NonNull Context context) {
+ if (mPermissionNames != null) {
+ return mPermissionNames;
+ }
+ final Set<String> healthPerms = HealthConnectManager.getHealthPermissions(context);
+ return mPermissionNames = healthPerms.toArray(new String[healthPerms.size()]);
+ }
+ }
+
/**
* The default policy for the foreground service types.
*
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 7475ef8..902f172 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -304,7 +304,7 @@
@UnsupportedAppUsage
void resumeAppSwitches();
boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId,
- int operationType);
+ int backupDestination);
void backupAgentCreated(in String packageName, in IBinder agent, int userId);
void unbindBackupAgent(in ApplicationInfo appInfo);
int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index b9a7186..8685259 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1148,12 +1148,26 @@
* @return the dimensions of system wallpaper
* @hide
*/
+ @Nullable
public Rect peekBitmapDimensions() {
return sGlobals.peekWallpaperDimensions(
mContext, true /* returnDefault */, mContext.getUserId());
}
/**
+ * Peek the dimensions of given wallpaper of the user without decoding it.
+ *
+ * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+ * {@link #FLAG_LOCK}.
+ * @return the dimensions of system wallpaper
+ * @hide
+ */
+ @Nullable
+ public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+ return peekBitmapDimensions();
+ }
+
+ /**
* Get an open, readable file descriptor to the given wallpaper image file.
* The caller is responsible for closing the file descriptor when done ingesting the file.
*
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index a4f612d..e323e89 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -20,7 +20,8 @@
import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.QueuedWork;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
import android.content.Context;
import android.content.ContextWrapper;
@@ -137,7 +138,7 @@
public abstract class BackupAgent extends ContextWrapper {
private static final String TAG = "BackupAgent";
private static final boolean DEBUG = false;
- private static final int DEFAULT_OPERATION_TYPE = OperationType.BACKUP;
+ private static final int DEFAULT_BACKUP_DESTINATION = BackupDestination.CLOUD;
/** @hide */
public static final int RESULT_SUCCESS = 0;
@@ -207,7 +208,7 @@
@Nullable private UserHandle mUser;
// This field is written from the main thread (in onCreate), and read in a Binder thread (in
// onFullBackup that is called from system_server via Binder).
- @OperationType private volatile int mOperationType = DEFAULT_OPERATION_TYPE;
+ @BackupDestination private volatile int mBackupDestination = DEFAULT_BACKUP_DESTINATION;
Handler getHandler() {
if (mHandler == null) {
@@ -265,13 +266,6 @@
}
/**
- * @hide
- */
- public void onCreate(UserHandle user) {
- onCreate(user, DEFAULT_OPERATION_TYPE);
- }
-
- /**
* Provided as a convenience for agent implementations that need an opportunity
* to do one-time initialization before the actual backup or restore operation
* is begun with information about the calling user.
@@ -279,14 +273,33 @@
*
* @hide
*/
- public void onCreate(UserHandle user, @OperationType int operationType) {
- // TODO: Instantiate with the correct type using a parameter.
- mLogger = new BackupRestoreEventLogger(BackupRestoreEventLogger.OperationType.BACKUP);
-
+ public void onCreate(UserHandle user) {
onCreate();
+ }
+ /**
+ * @deprecated Use {@link BackupAgent#onCreate(UserHandle, int, int)} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
mUser = user;
- mOperationType = operationType;
+ mBackupDestination = backupDestination;
+
+ onCreate(user);
+ }
+
+ /**
+ * @hide
+ */
+ public void onCreate(UserHandle user, @BackupDestination int backupDestination,
+ @OperationType int operationType) {
+ mUser = user;
+ mBackupDestination = backupDestination;
+ mLogger = new BackupRestoreEventLogger(operationType);
+
+ onCreate(user, backupDestination);
}
/**
@@ -433,7 +446,7 @@
*/
public void onFullBackup(FullBackupDataOutput data) throws IOException {
FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
- mOperationType);
+ mBackupDestination);
if (!backupScheme.isFullBackupEnabled(data.getTransportFlags())) {
return;
}
@@ -643,7 +656,7 @@
if (includeMap == null || includeMap.size() == 0) {
// Do entire sub-tree for the provided token.
fullBackupFileTree(packageName, domainToken,
- FullBackup.getBackupScheme(this, mOperationType)
+ FullBackup.getBackupScheme(this, mBackupDestination)
.tokenToDirectoryPath(domainToken),
filterSet, traversalExcludeSet, data);
} else if (includeMap.get(domainToken) != null) {
@@ -815,7 +828,7 @@
ArraySet<String> systemExcludes,
FullBackupDataOutput output) {
// Pull out the domain and set it aside to use when making the tarball.
- String domainPath = FullBackup.getBackupScheme(this, mOperationType)
+ String domainPath = FullBackup.getBackupScheme(this, mBackupDestination)
.tokenToDirectoryPath(domain);
if (domainPath == null) {
// Should never happen.
@@ -927,7 +940,7 @@
}
private boolean isFileEligibleForRestore(File destination) throws IOException {
- FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
+ FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mBackupDestination);
if (!bs.isFullRestoreEnabled()) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
@@ -1001,7 +1014,7 @@
+ " domain=" + domain + " relpath=" + path + " mode=" + mode
+ " mtime=" + mtime);
- basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath(
+ basePath = FullBackup.getBackupScheme(this, mBackupDestination).tokenToDirectoryPath(
domain);
if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
mode = -1; // < 0 is a token to skip attempting a chmod()
diff --git a/core/java/android/app/backup/BackupAnnotations.java b/core/java/android/app/backup/BackupAnnotations.java
new file mode 100644
index 0000000..d922861
--- /dev/null
+++ b/core/java/android/app/backup/BackupAnnotations.java
@@ -0,0 +1,62 @@
+/*
+ * 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.backup;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotations related to Android Backup&Restore.
+ *
+ * @hide
+ */
+public class BackupAnnotations {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ OperationType.UNKNOWN,
+ OperationType.BACKUP,
+ OperationType.RESTORE,
+ })
+ public @interface OperationType {
+ int UNKNOWN = -1;
+ int BACKUP = 0;
+ int RESTORE = 1;
+ }
+
+ /**
+ * Denotes where the backup data is going (e.g. to the cloud or directly to the other device)
+ * during backup or where it is coming from during restore.
+ *
+ * @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ BackupDestination.CLOUD,
+ BackupDestination.DEVICE_TRANSFER,
+ BackupDestination.ADB_BACKUP
+ })
+ public @interface BackupDestination {
+ // A cloud backup.
+ int CLOUD = 0;
+ // A device to device migration.
+ int DEVICE_TRANSFER = 1;
+ // An adb backup.
+ int ADB_BACKUP = 2;
+ }
+}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index d2c7972..378020f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -200,22 +200,6 @@
@SystemApi
public static final int ERROR_TRANSPORT_INVALID = -2;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- OperationType.BACKUP,
- OperationType.MIGRATION,
- OperationType.ADB_BACKUP,
- })
- public @interface OperationType {
- // A backup / restore to / from an off-device location, e.g. cloud.
- int BACKUP = 0;
- // A direct transfer to another device.
- int MIGRATION = 1;
- // Backup via adb, data saved on the host machine.
- int ADB_BACKUP = 3;
- }
-
private Context mContext;
@UnsupportedAppUsage
private static IBackupManager sService;
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 68740cb..f892833 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -16,13 +16,13 @@
package android.app.backup;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
+import android.app.backup.BackupAnnotations.OperationType;
import android.util.Slog;
import java.lang.annotation.Retention;
@@ -56,21 +56,6 @@
public static final int DATA_TYPES_ALLOWED = 15;
/**
- * Operation types for which this logger can be used.
- *
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- OperationType.BACKUP,
- OperationType.RESTORE
- })
- @interface OperationType {
- int BACKUP = 1;
- int RESTORE = 2;
- }
-
- /**
* Denotes that the annotated element identifies a data type as required by the logging methods
* of {@code BackupRestoreEventLogger}
*/
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index bf9a9b0..6371871 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,10 +16,9 @@
package android.app.backup;
-import static android.app.backup.BackupManager.OperationType;
-
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -123,20 +122,20 @@
/**
* Identify {@link BackupScheme} object by package and operation type
- * (see {@link OperationType}) it corresponds to.
+ * (see {@link BackupDestination}) it corresponds to.
*/
private static class BackupSchemeId {
final String mPackageName;
- @OperationType final int mOperationType;
+ @BackupDestination final int mBackupDestination;
- BackupSchemeId(String packageName, @OperationType int operationType) {
+ BackupSchemeId(String packageName, @BackupDestination int backupDestination) {
mPackageName = packageName;
- mOperationType = operationType;
+ mBackupDestination = backupDestination;
}
@Override
public int hashCode() {
- return Objects.hash(mPackageName, mOperationType);
+ return Objects.hash(mPackageName, mBackupDestination);
}
@Override
@@ -149,7 +148,7 @@
}
BackupSchemeId that = (BackupSchemeId) object;
return Objects.equals(mPackageName, that.mPackageName) &&
- Objects.equals(mOperationType, that.mOperationType);
+ Objects.equals(mBackupDestination, that.mBackupDestination);
}
}
@@ -164,19 +163,20 @@
new ArrayMap<>();
static synchronized BackupScheme getBackupScheme(Context context,
- @OperationType int operationType) {
- BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType);
+ @BackupDestination int backupDestination) {
+ BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(),
+ backupDestination);
BackupScheme backupSchemeForPackage =
kPackageBackupSchemeMap.get(backupSchemeId);
if (backupSchemeForPackage == null) {
- backupSchemeForPackage = new BackupScheme(context, operationType);
+ backupSchemeForPackage = new BackupScheme(context, backupDestination);
kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage);
}
return backupSchemeForPackage;
}
public static BackupScheme getBackupSchemeForTest(Context context) {
- BackupScheme testing = new BackupScheme(context, OperationType.BACKUP);
+ BackupScheme testing = new BackupScheme(context, BackupDestination.CLOUD);
testing.mExcludes = new ArraySet();
testing.mIncludes = new ArrayMap();
return testing;
@@ -303,7 +303,7 @@
final int mDataExtractionRules;
final int mFullBackupContent;
- @OperationType final int mOperationType;
+ @BackupDestination final int mBackupDestination;
final PackageManager mPackageManager;
final StorageManager mStorageManager;
final String mPackageName;
@@ -426,12 +426,12 @@
*/
ArraySet<PathWithRequiredFlags> mExcludes;
- BackupScheme(Context context, @OperationType int operationType) {
+ BackupScheme(Context context, @BackupDestination int backupDestination) {
ApplicationInfo applicationInfo = context.getApplicationInfo();
mDataExtractionRules = applicationInfo.dataExtractionRulesRes;
mFullBackupContent = applicationInfo.fullBackupContent;
- mOperationType = operationType;
+ mBackupDestination = backupDestination;
mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
mPackageManager = context.getPackageManager();
mPackageName = context.getPackageName();
@@ -568,7 +568,7 @@
}
try {
- parseSchemeForOperationType(mOperationType);
+ parseSchemeForBackupDestination(mBackupDestination);
} catch (PackageManager.NameNotFoundException e) {
// Throw it as an IOException
throw new IOException(e);
@@ -576,12 +576,12 @@
}
}
- private void parseSchemeForOperationType(@OperationType int operationType)
+ private void parseSchemeForBackupDestination(@BackupDestination int backupDestination)
throws PackageManager.NameNotFoundException, IOException, XmlPullParserException {
- String configSection = getConfigSectionForOperationType(operationType);
+ String configSection = getConfigSectionForBackupDestination(backupDestination);
if (configSection == null) {
- Slog.w(TAG, "Given operation type isn't supported by backup scheme: "
- + operationType);
+ Slog.w(TAG, "Given backup destination isn't supported by backup scheme: "
+ + backupDestination);
return;
}
@@ -600,7 +600,7 @@
}
}
- if (operationType == OperationType.MIGRATION
+ if (backupDestination == BackupDestination.DEVICE_TRANSFER
&& CompatChanges.isChangeEnabled(IGNORE_FULL_BACKUP_CONTENT_IN_D2D)) {
mIsUsingNewScheme = true;
return;
@@ -615,11 +615,12 @@
}
@Nullable
- private String getConfigSectionForOperationType(@OperationType int operationType) {
- switch (operationType) {
- case OperationType.BACKUP:
+ private String getConfigSectionForBackupDestination(
+ @BackupDestination int backupDestination) {
+ switch (backupDestination) {
+ case BackupDestination.CLOUD:
return ConfigSection.CLOUD_BACKUP;
- case OperationType.MIGRATION:
+ case BackupDestination.DEVICE_TRANSFER:
return ConfigSection.DEVICE_TRANSFER;
default:
return null;
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
index 710b8c4..ec10d84 100644
--- a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
@@ -16,8 +16,9 @@
package android.app.time;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
-import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString;
import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
@@ -86,12 +87,24 @@
public static final @ProviderStatus int PROVIDER_STATUS_IS_UNCERTAIN = 4;
/**
- * An instance that provides no information about algorithm status because the algorithm has not
- * yet reported. Effectively a "null" status placeholder.
+ * An instance used when the location algorithm is not supported by the device.
*/
- @NonNull
- public static final LocationTimeZoneAlgorithmStatus UNKNOWN =
- new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ public static final LocationTimeZoneAlgorithmStatus NOT_SUPPORTED =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+ PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null);
+
+ /**
+ * An instance used when the location algorithm is running, but has not reported an event.
+ */
+ public static final LocationTimeZoneAlgorithmStatus RUNNING_NOT_REPORTED =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+
+ /**
+ * An instance used when the location algorithm is supported but not running.
+ */
+ public static final LocationTimeZoneAlgorithmStatus NOT_RUNNING =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
private final @DetectionAlgorithmStatus int mStatus;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fcdf440..9f9fd3c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6201,7 +6201,7 @@
*/
@CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
@PackageManager.PermissionResult
- @PermissionMethod
+ @PermissionMethod(orSelf = true)
public abstract int checkCallingOrSelfPermission(@NonNull @PermissionName String permission);
/**
@@ -6269,7 +6269,7 @@
*
* @see #checkCallingOrSelfPermission(String)
*/
- @PermissionMethod
+ @PermissionMethod(orSelf = true)
public abstract void enforceCallingOrSelfPermission(
@NonNull @PermissionName String permission, @Nullable String message);
diff --git a/core/java/android/content/pm/PermissionMethod.java b/core/java/android/content/pm/PermissionMethod.java
index ba97342..647c696 100644
--- a/core/java/android/content/pm/PermissionMethod.java
+++ b/core/java/android/content/pm/PermissionMethod.java
@@ -33,4 +33,20 @@
*/
@Retention(CLASS)
@Target({METHOD})
-public @interface PermissionMethod {}
+public @interface PermissionMethod {
+ /**
+ * Hard-coded list of permissions checked by this method
+ */
+ @PermissionName String[] value() default {};
+ /**
+ * If true, the check passes if the caller
+ * has any ONE of the supplied permissions
+ */
+ boolean anyOf() default false;
+ /**
+ * Signifies that the permission check passes if
+ * the calling process OR the current process has
+ * the permission
+ */
+ boolean orSelf() default false;
+}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 3b7ed07..9e6cf62 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -308,7 +308,9 @@
* permissions:
* {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
* {@link android.Manifest.permission#BODY_SENSORS},
- * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+ * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS},
+ * or one of the {@code "android.permission.health.*"} permissions defined in the
+ * {@link android.healthconnect.HealthPermissions}.
*/
@RequiresPermission(
allOf = {
@@ -424,7 +426,7 @@
* android:name=".MySpecialForegroundService"
* android:foregroundServiceType="specialUse|foo">
* <property
- * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE""
+ * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
* android:value="foo"
* />
* </service>
diff --git a/telephony/java/android/telephony/CallState.aidl b/core/java/android/credentials/ClearCredentialStateRequest.aidl
similarity index 82%
rename from telephony/java/android/telephony/CallState.aidl
rename to core/java/android/credentials/ClearCredentialStateRequest.aidl
index dd5af8e..2679ee4 100644
--- a/telephony/java/android/telephony/CallState.aidl
+++ b/core/java/android/credentials/ClearCredentialStateRequest.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-package android.telephony;
+package android.credentials;
-parcelable CallState;
-
+parcelable ClearCredentialStateRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/ClearCredentialStateRequest.java b/core/java/android/credentials/ClearCredentialStateRequest.java
new file mode 100644
index 0000000..33afbed
--- /dev/null
+++ b/core/java/android/credentials/ClearCredentialStateRequest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A request class for clearing a user's credential state from the credential providers.
+ */
+public final class ClearCredentialStateRequest implements Parcelable {
+
+ /** The request data. */
+ @NonNull
+ private final Bundle mData;
+
+ /** Returns the request data. */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBundle(mData);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "ClearCredentialStateRequest {data=" + mData + "}";
+ }
+
+ /**
+ * Constructs a {@link ClearCredentialStateRequest}.
+ *
+ * @param data the request data
+ */
+ public ClearCredentialStateRequest(@NonNull Bundle data) {
+ mData = requireNonNull(data, "data must not be null");
+ }
+
+ private ClearCredentialStateRequest(@NonNull Parcel in) {
+ Bundle data = in.readBundle();
+ mData = data;
+ AnnotationValidations.validate(NonNull.class, null, mData);
+ }
+
+ public static final @NonNull Creator<ClearCredentialStateRequest> CREATOR =
+ new Creator<ClearCredentialStateRequest>() {
+ @Override
+ public ClearCredentialStateRequest[] newArray(int size) {
+ return new ClearCredentialStateRequest[size];
+ }
+
+ @Override
+ public ClearCredentialStateRequest createFromParcel(@NonNull Parcel in) {
+ return new ClearCredentialStateRequest(in);
+ }
+ };
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 04d57ad..1efac6c 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentSender;
@@ -65,17 +66,20 @@
* credential, display a picker when multiple credentials exist, etc.
*
* @param request the request specifying type(s) of credentials to get from the user
+ * @param activity the activity used to launch any UI needed
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
public void executeGetCredential(
@NonNull GetCredentialRequest request,
+ @NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
GetCredentialResponse, CredentialManagerException> callback) {
requireNonNull(request, "request must not be null");
+ requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -88,8 +92,7 @@
try {
cancelRemote = mService.executeGetCredential(
request,
- // TODO: use a real activity instead of context.
- new GetCredentialTransport(mContext, executor, callback),
+ new GetCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -107,17 +110,20 @@
* or storing the new credential, etc.
*
* @param request the request specifying type(s) of credentials to get from the user
+ * @param activity the activity used to launch any UI needed
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
public void executeCreateCredential(
@NonNull CreateCredentialRequest request,
+ @NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
CreateCredentialResponse, CredentialManagerException> callback) {
requireNonNull(request, "request must not be null");
+ requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -129,8 +135,7 @@
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeCreateCredential(request,
- // TODO: use a real activity instead of context.
- new CreateCredentialTransport(mContext, executor, callback),
+ new CreateCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -142,18 +147,24 @@
}
/**
- * Clears the current user credential session from all credential providers.
+ * Clears the current user credential state from all credential providers.
*
- * <p>Usually invoked after your user signs out of your app so that they will not be
- * automatically signed in the next time.
+ * You should invoked this api after your user signs out of your app to notify all credential
+ * providers that any stored credential session for the given app should be cleared.
*
+ * A credential provider may have stored an active credential session and use it to limit
+ * sign-in options for future get-credential calls. For example, it may prioritize the active
+ * credential over any other available credential. When your user explicitly signs out of your
+ * app and in order to get the holistic sign-in options the next time, you should call this API
+ * to let the provider clear any stored credential session.
+ *
+ * @param request the request data
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
- *
- * @hide
*/
- public void clearCredentialSession(
+ public void clearCredentialState(
+ @NonNull ClearCredentialStateRequest request,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
@@ -167,8 +178,8 @@
ICancellationSignal cancelRemote = null;
try {
- cancelRemote = mService.clearCredentialSession(
- new ClearCredentialSessionTransport(executor, callback),
+ cancelRemote = mService.clearCredentialState(request,
+ new ClearCredentialStateTransport(executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -182,14 +193,14 @@
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
- private final Context mActivityContext;
+ private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
GetCredentialResponse, CredentialManagerException> mCallback;
- private GetCredentialTransport(Context activityContext, Executor executor,
+ private GetCredentialTransport(Activity activity, Executor executor,
OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
- mActivityContext = activityContext;
+ mActivity = activity;
mExecutor = executor;
mCallback = callback;
}
@@ -197,7 +208,7 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent:"
+ pendingIntent.getIntentSender(), e);
@@ -220,14 +231,14 @@
private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
- private final Context mActivityContext;
+ private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
CreateCredentialResponse, CredentialManagerException> mCallback;
- private CreateCredentialTransport(Context activityContext, Executor executor,
+ private CreateCredentialTransport(Activity activity, Executor executor,
OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
- mActivityContext = activityContext;
+ mActivity = activity;
mExecutor = executor;
mCallback = callback;
}
@@ -235,7 +246,7 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent:"
+ pendingIntent.getIntentSender(), e);
@@ -255,14 +266,14 @@
}
}
- private static class ClearCredentialSessionTransport
- extends IClearCredentialSessionCallback.Stub {
+ private static class ClearCredentialStateTransport
+ extends IClearCredentialStateCallback.Stub {
// TODO: listen for cancellation to release callback.
private final Executor mExecutor;
private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
- private ClearCredentialSessionTransport(Executor executor,
+ private ClearCredentialStateTransport(Executor executor,
OutcomeReceiver<Void, CredentialManagerException> callback) {
mExecutor = executor;
mCallback = callback;
diff --git a/core/java/android/credentials/IClearCredentialSessionCallback.aidl b/core/java/android/credentials/IClearCredentialStateCallback.aidl
similarity index 88%
rename from core/java/android/credentials/IClearCredentialSessionCallback.aidl
rename to core/java/android/credentials/IClearCredentialStateCallback.aidl
index 903e7f5..f8b7ae44 100644
--- a/core/java/android/credentials/IClearCredentialSessionCallback.aidl
+++ b/core/java/android/credentials/IClearCredentialStateCallback.aidl
@@ -17,11 +17,11 @@
package android.credentials;
/**
- * Listener for clearCredentialSession request.
+ * Listener for clearCredentialState request.
*
* @hide
*/
-interface IClearCredentialSessionCallback {
+interface IClearCredentialStateCallback {
oneway void onSuccess();
oneway void onError(int errorCode, String message);
}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 35688d7..c5497bd 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -16,9 +16,10 @@
package android.credentials;
+import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
-import android.credentials.IClearCredentialSessionCallback;
+import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.os.ICancellationSignal;
@@ -34,5 +35,5 @@
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal clearCredentialSession(in IClearCredentialSessionCallback callback, String callingPackage);
+ @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 50551fee..f858227 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -245,11 +245,13 @@
* applications is not guaranteed to be supported, however.</p>
*
* <p>For concurrent operation, in chronological order :
- * - Applications must first close any open cameras that have sessions configured, using
- * {@link CameraDevice#close}.
- * - All camera devices intended to be operated concurrently, must be opened using
- * {@link #openCamera}, before configuring sessions on any of the camera devices.</p>
- *
+ * <ul>
+ * <li> Applications must first close any open cameras that have sessions configured, using
+ * {@link CameraDevice#close}. </li>
+ * <li> All camera devices intended to be operated concurrently, must be opened using
+ * {@link #openCamera}, before configuring sessions on any of the camera devices.</li>
+ *</ul>
+ *</p>
* <p>Each device in a combination, is guaranteed to support stream combinations which may be
* obtained by querying {@link #getCameraCharacteristics} for the key
* {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}.</p>
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 553a324..7757081 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -27,8 +27,6 @@
/**
* An action defined by the provider that intents into the provider's app for specific
* user actions.
- *
- * @hide
*/
public final class Action implements Parcelable {
/** Slice object containing display content to be displayed with this action on the UI. */
@@ -39,6 +37,13 @@
/**
* Constructs an action to be displayed on the UI.
*
+ * <p> Actions must be used for any provider related operations, such as opening the provider
+ * app, intenting straight into certain app activities like 'manage credentials', top
+ * level authentication before displaying any content etc.
+ *
+ * <p> See details on usage of {@code Action} for various actionable entries in
+ * {@link BeginCreateCredentialResponse} and {@link GetCredentialsResponse}.
+ *
* @param slice the display content to be displayed on the UI, along with this action
* @param pendingIntent the intent to be invoked when the user selects this action
*/
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
new file mode 100644
index 0000000..30cab8d
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable BeginCreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.java b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
new file mode 100644
index 0000000..1918d8c
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.service.credentials;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request for beginning a create credential request.
+ *
+ * See {@link BeginCreateCredentialResponse} for the counterpart response
+ */
+public final class BeginCreateCredentialRequest implements Parcelable {
+ private final @NonNull String mCallingPackage;
+ private final @NonNull String mType;
+ private final @NonNull Bundle mData;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @throws IllegalArgumentException If {@code callingPackage}, or {@code type} string is
+ * null or empty.
+ * @throws NullPointerException If {@code data} is null.
+ */
+ public BeginCreateCredentialRequest(@NonNull String callingPackage,
+ @NonNull String type, @NonNull Bundle data) {
+ mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage,
+ "callingPackage must not be null or empty");
+ mType = Preconditions.checkStringNotEmpty(type,
+ "type must not be null or empty");
+ mData = Objects.requireNonNull(data, "data must not be null");
+ }
+
+ private BeginCreateCredentialRequest(@NonNull Parcel in) {
+ mCallingPackage = in.readString8();
+ mType = in.readString8();
+ mData = in.readBundle(Bundle.class.getClassLoader());
+ }
+
+ public static final @NonNull Creator<BeginCreateCredentialRequest> CREATOR =
+ new Creator<BeginCreateCredentialRequest>() {
+ @Override
+ public BeginCreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+ return new BeginCreateCredentialRequest(in);
+ }
+
+ @Override
+ public BeginCreateCredentialRequest[] newArray(int size) {
+ return new BeginCreateCredentialRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mCallingPackage);
+ dest.writeString8(mType);
+ dest.writeBundle(mData);
+ }
+
+ /** Returns the calling package of the calling app. */
+ @NonNull
+ public String getCallingPackage() {
+ return mCallingPackage;
+ }
+
+ /** Returns the type of the credential to be created. */
+ @NonNull
+ public String getType() {
+ return mType;
+ }
+
+ /** Returns the data to be used while resolving the credential to create. */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
similarity index 93%
rename from core/java/android/service/credentials/CreateCredentialResponse.aidl
rename to core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
index 73c9147..d2a1408 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.aidl
@@ -16,4 +16,4 @@
package android.service.credentials;
-parcelable CreateCredentialResponse;
+parcelable BeginCreateCredentialResponse;
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
new file mode 100644
index 0000000..022678e
--- /dev/null
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -0,0 +1,150 @@
+/*
+ * 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.service.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response to a {@link BeginCreateCredentialRequest}.
+ */
+public final class BeginCreateCredentialResponse implements Parcelable {
+ private final @NonNull List<CreateEntry> mCreateEntries;
+ private final @Nullable CreateEntry mRemoteCreateEntry;
+
+ private BeginCreateCredentialResponse(@NonNull Parcel in) {
+ List<CreateEntry> createEntries = new ArrayList<>();
+ in.readTypedList(createEntries, CreateEntry.CREATOR);
+ mCreateEntries = createEntries;
+ mRemoteCreateEntry = in.readTypedObject(CreateEntry.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mCreateEntries);
+ dest.writeTypedObject(mRemoteCreateEntry, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<BeginCreateCredentialResponse> CREATOR =
+ new Creator<BeginCreateCredentialResponse>() {
+ @Override
+ public BeginCreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+ return new BeginCreateCredentialResponse(in);
+ }
+
+ @Override
+ public BeginCreateCredentialResponse[] newArray(int size) {
+ return new BeginCreateCredentialResponse[size];
+ }
+ };
+
+ /* package-private */ BeginCreateCredentialResponse(
+ @NonNull List<CreateEntry> createEntries,
+ @Nullable CreateEntry remoteCreateEntry) {
+ this.mCreateEntries = createEntries;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mCreateEntries);
+ this.mRemoteCreateEntry = remoteCreateEntry;
+ }
+
+ /** Returns the list of create entries to be displayed on the UI. */
+ public @NonNull List<CreateEntry> getCreateEntries() {
+ return mCreateEntries;
+ }
+
+ /** Returns the remote create entry to be displayed on the UI. */
+ public @Nullable CreateEntry getRemoteCreateEntry() {
+ return mRemoteCreateEntry;
+ }
+
+ /**
+ * A builder for {@link BeginCreateCredentialResponse}
+ */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ public static final class Builder {
+ private @NonNull List<CreateEntry> mCreateEntries = new ArrayList<>();
+ private @Nullable CreateEntry mRemoteCreateEntry;
+
+ /**
+ * Sets the list of create entries to be shown on the UI.
+ *
+ * @throws IllegalArgumentException If {@code createEntries} is empty.
+ * @throws NullPointerException If {@code createEntries} is null, or any of its elements
+ * are null.
+ */
+ public @NonNull Builder setCreateEntries(@NonNull List<CreateEntry> createEntries) {
+ Preconditions.checkCollectionNotEmpty(createEntries, "createEntries");
+ mCreateEntries = Preconditions.checkCollectionElementsNotNull(
+ createEntries, "createEntries");
+ return this;
+ }
+
+ /**
+ * Adds an entry to the list of create entries to be shown on the UI.
+ *
+ * @throws NullPointerException If {@code createEntry} is null.
+ */
+ public @NonNull Builder addCreateEntry(@NonNull CreateEntry createEntry) {
+ mCreateEntries.add(Objects.requireNonNull(createEntry));
+ return this;
+ }
+
+ /**
+ * Sets a remote create entry to be shown on the UI. Provider must set this entry if they
+ * wish to create the credential on a different device.
+ *
+ * <p> When constructing the {@link CreateEntry} object, the {@code pendingIntent} must be
+ * set such that it leads to an activity that can provide UI to fulfill the request on
+ * a remote device. When user selects this {@code remoteCreateEntry}, the system will
+ * invoke the {@code pendingIntent} set on the {@link CreateEntry}.
+ *
+ * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+ * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+ * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESULT} key should be populated
+ * with a {@link android.credentials.CreateCredentialResponse} object.
+ */
+ public @NonNull Builder setRemoteCreateEntry(@Nullable CreateEntry remoteCreateEntry) {
+ mRemoteCreateEntry = remoteCreateEntry;
+ return this;
+ }
+
+ /**
+ * Builds a new instance of {@link BeginCreateCredentialResponse}.
+ *
+ * @throws NullPointerException If {@code createEntries} is null.
+ * @throws IllegalArgumentException If {@code createEntries} is empty.
+ */
+ public @NonNull BeginCreateCredentialResponse build() {
+ Preconditions.checkCollectionNotEmpty(mCreateEntries, "createEntries must "
+ + "not be null, or empty");
+ return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.aidl b/core/java/android/service/credentials/CreateCredentialRequest.aidl
deleted file mode 100644
index eb7fba9..0000000
--- a/core/java/android/service/credentials/CreateCredentialRequest.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.service.credentials;
-
-parcelable CreateCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
index e6da349..aee85ab 100644
--- a/core/java/android/service/credentials/CreateCredentialRequest.java
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -27,8 +27,6 @@
/**
* Request for creating a credential.
- *
- * @hide
*/
public final class CreateCredentialRequest implements Parcelable {
private final @NonNull String mCallingPackage;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
deleted file mode 100644
index f69dca8..0000000
--- a/core/java/android/service/credentials/CreateCredentialResponse.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 android.service.credentials;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Response to a {@link CreateCredentialRequest}.
- *
- * @hide
- */
-public final class CreateCredentialResponse implements Parcelable {
- private final @NonNull List<SaveEntry> mSaveEntries;
- private final @Nullable Action mRemoteSaveEntry;
- //TODO : Add actions if needed
-
- private CreateCredentialResponse(@NonNull Parcel in) {
- List<SaveEntry> saveEntries = new ArrayList<>();
- in.readTypedList(saveEntries, SaveEntry.CREATOR);
- mSaveEntries = saveEntries;
- mRemoteSaveEntry = in.readTypedObject(Action.CREATOR);
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedList(mSaveEntries);
- dest.writeTypedObject(mRemoteSaveEntry, flags);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public static final @NonNull Creator<CreateCredentialResponse> CREATOR =
- new Creator<CreateCredentialResponse>() {
- @Override
- public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
- return new CreateCredentialResponse(in);
- }
-
- @Override
- public CreateCredentialResponse[] newArray(int size) {
- return new CreateCredentialResponse[size];
- }
- };
-
- /* package-private */ CreateCredentialResponse(
- @NonNull List<SaveEntry> saveEntries,
- @Nullable Action remoteSaveEntry) {
- this.mSaveEntries = saveEntries;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mSaveEntries);
- this.mRemoteSaveEntry = remoteSaveEntry;
- }
-
- /** Returns the list of save entries to be displayed on the UI. */
- public @NonNull List<SaveEntry> getSaveEntries() {
- return mSaveEntries;
- }
-
- /** Returns the remote save entry to be displayed on the UI. */
- public @NonNull Action getRemoteSaveEntry() {
- return mRemoteSaveEntry;
- }
-
- /**
- * A builder for {@link CreateCredentialResponse}
- */
- @SuppressWarnings("WeakerAccess")
- public static final class Builder {
- private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
- private @Nullable Action mRemoteSaveEntry;
-
- /**
- * Sets the list of save entries to be shown on the UI.
- *
- * @throws IllegalArgumentException If {@code saveEntries} is empty.
- * @throws NullPointerException If {@code saveEntries} is null, or any of its elements
- * are null.
- */
- public @NonNull Builder setSaveEntries(@NonNull List<SaveEntry> saveEntries) {
- Preconditions.checkCollectionNotEmpty(saveEntries, "saveEntries");
- mSaveEntries = Preconditions.checkCollectionElementsNotNull(
- saveEntries, "saveEntries");
- return this;
- }
-
- /**
- * Adds an entry to the list of save entries to be shown on the UI.
- *
- * @throws NullPointerException If {@code saveEntry} is null.
- */
- public @NonNull Builder addSaveEntry(@NonNull SaveEntry saveEntry) {
- mSaveEntries.add(Objects.requireNonNull(saveEntry));
- return this;
- }
-
- /**
- * Sets a remote save entry to be shown on the UI.
- */
- public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) {
- mRemoteSaveEntry = remoteSaveEntry;
- return this;
- }
-
- /**
- * Builds the instance.
- *
- * @throws IllegalArgumentException If {@code saveEntries} is empty.
- */
- public @NonNull CreateCredentialResponse build() {
- Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
- + "not be empty");
- return new CreateCredentialResponse(
- mSaveEntries,
- mRemoteSaveEntry);
- }
- }
-}
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/CreateEntry.java
similarity index 77%
rename from core/java/android/service/credentials/SaveEntry.java
rename to core/java/android/service/credentials/CreateEntry.java
index 55ff6ff..eb25e25 100644
--- a/core/java/android/service/credentials/SaveEntry.java
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -25,27 +25,25 @@
/**
* An entry to be shown on the UI. This entry represents where the credential to be created will
* be stored. Examples include user's account, family group etc.
- *
- * @hide
*/
-public final class SaveEntry implements Parcelable {
+public final class CreateEntry implements Parcelable {
private final @NonNull Slice mSlice;
private final @NonNull PendingIntent mPendingIntent;
- private SaveEntry(@NonNull Parcel in) {
+ private CreateEntry(@NonNull Parcel in) {
mSlice = in.readTypedObject(Slice.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
}
- public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
+ public static final @NonNull Creator<CreateEntry> CREATOR = new Creator<CreateEntry>() {
@Override
- public SaveEntry createFromParcel(@NonNull Parcel in) {
- return new SaveEntry(in);
+ public CreateEntry createFromParcel(@NonNull Parcel in) {
+ return new CreateEntry(in);
}
@Override
- public SaveEntry[] newArray(int size) {
- return new SaveEntry[size];
+ public CreateEntry[] newArray(int size) {
+ return new CreateEntry[size];
}
};
@@ -61,12 +59,12 @@
}
/**
- * Constructs a save entry to be displayed on the UI.
+ * Constructs a CreateEntry to be displayed on the UI.
*
* @param slice the display content to be displayed on the UI, along with this entry
* @param pendingIntent the intent to be invoked when the user selects this entry
*/
- public SaveEntry(
+ public CreateEntry(
@NonNull Slice slice,
@NonNull PendingIntent pendingIntent) {
this.mSlice = slice;
@@ -77,12 +75,12 @@
NonNull.class, null, mPendingIntent);
}
- /** Returns the content to be displayed with this save entry on the UI. */
+ /** Returns the content to be displayed with this create entry on the UI. */
public @NonNull Slice getSlice() {
return mSlice;
}
- /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
+ /** Returns the pendingIntent to be invoked when this create entry on the UI is selectcd. */
public @NonNull PendingIntent getPendingIntent() {
return mPendingIntent;
}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 98c537a..941db02b 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -31,8 +31,6 @@
/**
* A credential entry that is displayed on the account selector UI. Each entry corresponds to
* something that the user can select.
- *
- * @hide
*/
public final class CredentialEntry implements Parcelable {
/** The type of the credential entry to be shown on the UI. */
@@ -145,61 +143,67 @@
private boolean mAutoSelectAllowed = false;
/**
- * Builds the instance.
+ * Creates a builder for a {@link CredentialEntry} that should invoke a
+ * {@link PendingIntent} when selected by the user.
+ *
+ * <p>The {@code pendingIntent} can be used to launch activities that require some user
+ * engagement before getting the credential corresponding to this entry,
+ * e.g. authentication, confirmation etc.
+ * Once the activity fulfills the required user engagement, the
+ * {@link android.app.Activity} result should be set to
+ * {@link android.app.Activity#RESULT_OK}, and the
+ * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} must be set with a
+ * {@link Credential} object.
+ *
* @param type the type of credential underlying this credential entry
* @param slice the content to be displayed with this entry on the UI
+ * @param pendingIntent the pendingIntent to be invoked when this entry is selected by the
+ * user
*
- * @throws IllegalArgumentException If {@code type} is null or empty.
- * @throws NullPointerException If {@code slice} is null.
+ * @throws NullPointerException If {@code slice}, or {@code pendingIntent} is null.
+ * @throws IllegalArgumentException If {@code type} is null or empty, or if
+ * {@code pendingIntent} was not created with {@link PendingIntent#getActivity}
+ * or {@link PendingIntent#getActivities}.
*/
- public Builder(@NonNull String type, @NonNull Slice slice) {
+ public Builder(@NonNull String type, @NonNull Slice slice,
+ @NonNull PendingIntent pendingIntent) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+ "null, or empty");
mSlice = Objects.requireNonNull(slice,
"slice must not be null");
+ mPendingIntent = Objects.requireNonNull(pendingIntent,
+ "pendingIntent must not be null");
+ if (!mPendingIntent.isActivity()) {
+ throw new IllegalStateException("Pending intent must start an activity");
+ }
}
/**
- * Sets the pendingIntent to be invoked if the user selects this entry.
+ * Creates a builder for a {@link CredentialEntry} that contains a {@link Credential},
+ * and does not require further action.
+ * @param type the type of credential underlying this credential entry
+ * @param slice the content to be displayed with this entry on the UI
+ * @param credential the credential to be returned to the client app, when this entry is
+ * selected by the user
*
- * The pending intent can be used to launch activities that require some user engagement
- * before getting the credential corresponding to this entry, e.g. authentication,
- * confirmation etc.
- * Once the activity fulfills the required user engagement, a {@link Credential} object
- * must be returned as an extra on activity finish.
- *
- * @throws IllegalStateException If {@code credential} is already set. Must either set the
- * {@code credential}, or the {@code pendingIntent}.
+ * @throws IllegalArgumentException If {@code type} is null or empty.
+ * @throws NullPointerException If {@code slice}, or {@code credential} is null.
*/
- public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
- if (pendingIntent != null) {
- Preconditions.checkState(mCredential == null,
- "credential is already set. Cannot set both the pendingIntent "
- + "and the credential");
- }
- mPendingIntent = pendingIntent;
- return this;
- }
-
- /**
- * Sets the credential to be used, if the user selects this entry.
- *
- * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set
- * the {@code pendingIntent}, or the {@code credential}.
- */
- public @NonNull Builder setCredential(@Nullable Credential credential) {
- if (credential != null) {
- Preconditions.checkState(mPendingIntent == null,
- "pendingIntent is already set. Cannot set both the "
- + "pendingIntent and the credential");
- }
- mCredential = credential;
- return this;
+ public Builder(@NonNull String type, @NonNull Slice slice, @NonNull Credential credential) {
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+ + "null, or empty");
+ mSlice = Objects.requireNonNull(slice,
+ "slice must not be null");
+ mCredential = Objects.requireNonNull(credential,
+ "credential must not be null");
}
/**
* Sets whether the entry is allowed to be auto selected by the framework.
* The default value is set to false.
+ *
+ * <p> The entry is only auto selected if it is the only entry on the user selector,
+ * AND the developer has also enabled auto select, while building the request.
*/
public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) {
mAutoSelectAllowed = autoSelectAllowed;
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
index 06f0052..02b7443 100644
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ b/core/java/android/service/credentials/CredentialProviderException.java
@@ -24,8 +24,6 @@
/**
* Contains custom exceptions to be used by credential providers on failure.
- *
- * @hide
*/
public class CredentialProviderException extends Exception {
public static final int ERROR_UNKNOWN = 0;
@@ -59,6 +57,11 @@
@Retention(RetentionPolicy.SOURCE)
public @interface CredentialProviderError { }
+ public CredentialProviderException(@CredentialProviderError int errorCode,
+ @NonNull String message, @NonNull Throwable cause) {
+ super(message, cause);
+ mErrorCode = errorCode;
+ }
public CredentialProviderException(@CredentialProviderError int errorCode,
@NonNull String message) {
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 24b7c3c..32646e6 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -37,30 +37,63 @@
/**
* Service to be extended by credential providers, in order to return user credentials
* to the framework.
- *
- * @hide
*/
public abstract class CredentialProviderService extends Service {
- /** Extra to be used by provider to populate the credential when ending the activity started
- * through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
- public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
- "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
-
- /** Extra to be used by provider to populate the {@link CredentialsDisplayContent} when
- * an authentication action entry is selected. **/
- public static final String EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT =
- "android.service.credentials.extra.GET_CREDENTIALS_DISPLAY_CONTENT";
+ /**
+ * Intent extra: The {@link android.credentials.CreateCredentialRequest} attached with
+ * the {@code pendingIntent} that is invoked when the user selects a {@link CreateEntry}
+ * returned as part of the {@link BeginCreateCredentialResponse}
+ *
+ * <p>
+ * Type: {@link android.credentials.CreateCredentialRequest}
+ */
+ public static final String EXTRA_CREATE_CREDENTIAL_REQUEST =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
/**
- * Provider must read the value against this extra to receive the complete create credential
- * request parameters, when a pending intent is launched.
+ * Intent extra: The result of a create flow operation, to be set on finish of the
+ * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+ * a {@link CreateEntry}.
+ *
+ * <p>
+ * Type: {@link android.credentials.CreateCredentialResponse}
*/
- public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
- "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
+ public static final String EXTRA_CREATE_CREDENTIAL_RESULT =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
- /** Extra to be used by the provider when setting the credential result. */
- public static final String EXTRA_GET_CREDENTIAL =
- "android.service.credentials.extra.GET_CREDENTIAL";
+ /**
+ * Intent extra: The result of a get credential flow operation, to be set on finish of the
+ * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
+ * a {@link CredentialEntry}.
+ *
+ * <p>
+ * Type: {@link android.credentials.Credential}
+ */
+ public static final String EXTRA_CREDENTIAL_RESULT =
+ "android.service.credentials.extra.CREDENTIAL_RESULT";
+
+ /**
+ * Intent extra: The result of an authentication flow, to be set on finish of the
+ * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on
+ * a {@link GetCredentialsResponse}. This result should contain the actual content, including
+ * credential entries and action entries, to be shown on the selector.
+ *
+ * <p>
+ * Type: {@link CredentialsResponseContent}
+ */
+ public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT =
+ "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+
+ /**
+ * Intent extra: The error result of any {@link android.app.PendingIntent} flow, to be set
+ * on finish of the corresponding {@link android.app.Activity}. This result should contain an
+ * error code, representing the error encountered by the provider.
+ *
+ * <p>
+ * Type: {@link String}
+ */
+ public static final String EXTRA_ERROR =
+ "android.service.credentials.extra.ERROR";
private static final String TAG = "CredProviderService";
@@ -129,20 +162,21 @@
}
@Override
- public ICancellationSignal onCreateCredential(CreateCredentialRequest request,
- ICreateCredentialCallback callback) {
+ public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request,
+ IBeginCreateCredentialCallback callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(callback);
ICancellationSignal transport = CancellationSignal.createTransport();
mHandler.sendMessage(obtainMessage(
- CredentialProviderService::onCreateCredential,
+ CredentialProviderService::onBeginCreateCredential,
CredentialProviderService.this, request,
CancellationSignal.fromTransport(transport),
- new OutcomeReceiver<CreateCredentialResponse, CredentialProviderException>() {
+ new OutcomeReceiver<
+ BeginCreateCredentialResponse, CredentialProviderException>() {
@Override
- public void onResult(CreateCredentialResponse result) {
+ public void onResult(BeginCreateCredentialResponse result) {
try {
callback.onSuccess(result);
} catch (RemoteException e) {
@@ -182,8 +216,8 @@
* the android system.
* @param callback Object used to relay the response of the credential creation request.
*/
- public abstract void onCreateCredential(@NonNull CreateCredentialRequest request,
+ public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
@NonNull CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<CreateCredentialResponse,
+ @NonNull OutcomeReceiver<BeginCreateCredentialResponse,
CredentialProviderException> callback);
}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsResponseContent.java
similarity index 64%
rename from core/java/android/service/credentials/CredentialsDisplayContent.java
rename to core/java/android/service/credentials/CredentialsResponseContent.java
index 4b23800..32cab50 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsResponseContent.java
@@ -28,12 +28,10 @@
import java.util.Objects;
/**
- * Content to be displayed on the account selector UI, including credential entries,
- * actions etc.
- *
- * @hide
+ * The content to be displayed on the account selector UI, including credential entries,
+ * actions etc. Returned as part of {@link GetCredentialsResponse}
*/
-public final class CredentialsDisplayContent implements Parcelable {
+public final class CredentialsResponseContent implements Parcelable {
/** List of credential entries to be displayed on the UI. */
private final @NonNull List<CredentialEntry> mCredentialEntries;
@@ -41,36 +39,36 @@
private final @NonNull List<Action> mActions;
/** Remote credential entry to get the response from a different device. */
- private final @Nullable Action mRemoteCredentialEntry;
+ private final @Nullable CredentialEntry mRemoteCredentialEntry;
- private CredentialsDisplayContent(@NonNull List<CredentialEntry> credentialEntries,
+ private CredentialsResponseContent(@NonNull List<CredentialEntry> credentialEntries,
@NonNull List<Action> actions,
- @Nullable Action remoteCredentialEntry) {
+ @Nullable CredentialEntry remoteCredentialEntry) {
mCredentialEntries = credentialEntries;
mActions = actions;
mRemoteCredentialEntry = remoteCredentialEntry;
}
- private CredentialsDisplayContent(@NonNull Parcel in) {
+ private CredentialsResponseContent(@NonNull Parcel in) {
List<CredentialEntry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
mCredentialEntries = credentialEntries;
List<Action> actions = new ArrayList<>();
in.readTypedList(actions, Action.CREATOR);
mActions = actions;
- mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR);
+ mRemoteCredentialEntry = in.readTypedObject(CredentialEntry.CREATOR);
}
- public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
- new Creator<CredentialsDisplayContent>() {
+ public static final @NonNull Creator<CredentialsResponseContent> CREATOR =
+ new Creator<CredentialsResponseContent>() {
@Override
- public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) {
- return new CredentialsDisplayContent(in);
+ public CredentialsResponseContent createFromParcel(@NonNull Parcel in) {
+ return new CredentialsResponseContent(in);
}
@Override
- public CredentialsDisplayContent[] newArray(int size) {
- return new CredentialsDisplayContent[size];
+ public CredentialsResponseContent[] newArray(int size) {
+ return new CredentialsResponseContent[size];
}
};
@@ -103,22 +101,34 @@
/**
* Returns the remote credential entry to be displayed on the UI.
*/
- public @Nullable Action getRemoteCredentialEntry() {
+ public @Nullable CredentialEntry getRemoteCredentialEntry() {
return mRemoteCredentialEntry;
}
/**
- * Builds an instance of {@link CredentialsDisplayContent}.
+ * Builds an instance of {@link CredentialsResponseContent}.
*/
public static final class Builder {
private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
private List<Action> mActions = new ArrayList<>();
- private Action mRemoteCredentialEntry;
+ private CredentialEntry mRemoteCredentialEntry;
/**
- * Sets the remote credential entry to be displayed on the UI.
+ * Sets a remote credential entry to be shown on the UI. Provider must set this if they
+ * wish to get the credential from a different device.
+ *
+ * <p> When constructing the {@link CredentialEntry} object, the {@code pendingIntent}
+ * must be set such that it leads to an activity that can provide UI to fulfill the request
+ * on a remote device. When user selects this {@code remoteCredentialEntry}, the system will
+ * invoke the {@code pendingIntent} set on the {@link CredentialEntry}.
+ *
+ * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
+ * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
+ * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} key should be populated
+ * with a {@link android.credentials.Credential} object.
*/
- public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
+ public @NonNull Builder setRemoteCredentialEntry(@Nullable CredentialEntry
+ remoteCredentialEntry) {
mRemoteCredentialEntry = remoteCredentialEntry;
return this;
}
@@ -138,6 +148,11 @@
* Adds an {@link Action} to the list of actions to be displayed on
* the UI.
*
+ * <p> An {@code action} must be used for independent user actions,
+ * such as opening the app, intenting directly into a certain app activity etc. The
+ * {@code pendingIntent} set with the {@code action} must invoke the corresponding
+ * activity.
+ *
* @throws NullPointerException If {@code action} is null.
*/
public @NonNull Builder addAction(@NonNull Action action) {
@@ -175,17 +190,16 @@
/**
* Builds a {@link GetCredentialsResponse} instance.
*
- * @throws NullPointerException If {@code credentialEntries} is null.
- * @throws IllegalStateException if both {@code credentialEntries} and
- * {@code actions} are empty.
+ * @throws IllegalStateException if {@code credentialEntries}, {@code actions}
+ * and {@code remoteCredentialEntry} are all null or empty.
*/
- public @NonNull CredentialsDisplayContent build() {
+ public @NonNull CredentialsResponseContent build() {
if (mCredentialEntries != null && mCredentialEntries.isEmpty()
- && mActions != null && mActions.isEmpty()) {
+ && mActions != null && mActions.isEmpty() && mRemoteCredentialEntry == null) {
throw new IllegalStateException("credentialEntries and actions must not both "
+ "be empty");
}
- return new CredentialsDisplayContent(mCredentialEntries, mActions,
+ return new CredentialsResponseContent(mCredentialEntries, mActions,
mRemoteCredentialEntry);
}
}
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index 03ba20e..9052b54 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -29,8 +29,6 @@
/**
* Request for getting user's credentials from a given credential provider.
- *
- * @hide
*/
public final class GetCredentialsRequest implements Parcelable {
/** Calling package of the app requesting for credentials. */
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java
index 979a699..5263141 100644
--- a/core/java/android/service/credentials/GetCredentialsResponse.java
+++ b/core/java/android/service/credentials/GetCredentialsResponse.java
@@ -26,12 +26,10 @@
/**
* Response from a credential provider, containing credential entries and other associated
* data to be shown on the account selector UI.
- *
- * @hide
*/
public final class GetCredentialsResponse implements Parcelable {
/** Content to be used for the UI. */
- private final @Nullable CredentialsDisplayContent mCredentialsDisplayContent;
+ private final @Nullable CredentialsResponseContent mCredentialsResponseContent;
/**
* Authentication action that must be launched and completed before showing any content
@@ -40,11 +38,17 @@
private final @Nullable Action mAuthenticationAction;
/**
- * Creates a {@link GetCredentialsRequest} instance with an authentication action set.
+ * Creates a {@link GetCredentialsResponse} instance with an authentication {@link Action} set.
* Providers must use this method when no content can be shown before authentication.
*
- * Once the authentication action activity is launched, and the user is authenticated, providers
- * should create another response with {@link CredentialsDisplayContent} using
+ * <p> When the user selects this {@code authenticationAction}, the system invokes the
+ * corresponding {@code pendingIntent}. Once the authentication flow is complete,
+ * the {@link android.app.Activity} result should be set
+ * to {@link android.app.Activity#RESULT_OK} and the
+ * {@link CredentialProviderService#EXTRA_GET_CREDENTIALS_CONTENT_RESULT} extra should be set
+ * with a fully populated {@link CredentialsResponseContent} object.
+ * the authentication action activity is launched, and the user is authenticated, providers
+ * should create another response with {@link CredentialsResponseContent} using
* {@code createWithDisplayContent}, and add that response to the result of the authentication
* activity.
*
@@ -58,27 +62,27 @@
}
/**
- * Creates a {@link GetCredentialsRequest} instance with display content to be shown on the UI.
+ * Creates a {@link GetCredentialsRequest} instance with content to be shown on the UI.
* Providers must use this method when there is content to be shown without top level
- * authentication required.
+ * authentication required, including credential entries, action entries or a remote entry,
*
- * @throws NullPointerException If {@code credentialsDisplayContent} is null.
+ * @throws NullPointerException If {@code credentialsResponseContent} is null.
*/
- public static @NonNull GetCredentialsResponse createWithDisplayContent(
- @NonNull CredentialsDisplayContent credentialsDisplayContent) {
- Objects.requireNonNull(credentialsDisplayContent,
- "credentialsDisplayContent must not be null");
- return new GetCredentialsResponse(credentialsDisplayContent, null);
+ public static @NonNull GetCredentialsResponse createWithResponseContent(
+ @NonNull CredentialsResponseContent credentialsResponseContent) {
+ Objects.requireNonNull(credentialsResponseContent,
+ "credentialsResponseContent must not be null");
+ return new GetCredentialsResponse(credentialsResponseContent, null);
}
- private GetCredentialsResponse(@Nullable CredentialsDisplayContent credentialsDisplayContent,
+ private GetCredentialsResponse(@Nullable CredentialsResponseContent credentialsResponseContent,
@Nullable Action authenticationAction) {
- mCredentialsDisplayContent = credentialsDisplayContent;
+ mCredentialsResponseContent = credentialsResponseContent;
mAuthenticationAction = authenticationAction;
}
private GetCredentialsResponse(@NonNull Parcel in) {
- mCredentialsDisplayContent = in.readTypedObject(CredentialsDisplayContent.CREATOR);
+ mCredentialsResponseContent = in.readTypedObject(CredentialsResponseContent.CREATOR);
mAuthenticationAction = in.readTypedObject(Action.CREATOR);
}
@@ -102,23 +106,23 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedObject(mCredentialsDisplayContent, flags);
+ dest.writeTypedObject(mCredentialsResponseContent, flags);
dest.writeTypedObject(mAuthenticationAction, flags);
}
/**
- * Returns the authentication action to be invoked before any other content
- * can be shown to the user.
+ * If this response represents a top level authentication action, returns the authentication
+ * action to be invoked before any other content can be shown to the user.
*/
public @Nullable Action getAuthenticationAction() {
return mAuthenticationAction;
}
/**
- * Returns the credentialDisplayContent that does not require authentication, and
- * can be shown to the user on the account selector UI.
+ * Returns the actual content to be displayed on the selector, if this response does not
+ * require any top level authentication.
*/
- public @Nullable CredentialsDisplayContent getCredentialsDisplayContent() {
- return mCredentialsDisplayContent;
+ public @Nullable CredentialsResponseContent getCredentialsResponseContent() {
+ return mCredentialsResponseContent;
}
}
diff --git a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
new file mode 100644
index 0000000..ec0bc36
--- /dev/null
+++ b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.BeginCreateCredentialResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface IBeginCreateCredentialCallback {
+ void onSuccess(in BeginCreateCredentialResponse request);
+ void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICreateCredentialCallback.aidl b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
deleted file mode 100644
index 4cc76a4..0000000
--- a/core/java/android/service/credentials/ICreateCredentialCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.service.credentials;
-
-import android.service.credentials.CreateCredentialResponse;
-
-/**
- * Interface from the system to a credential provider service.
- *
- * @hide
- */
-oneway interface ICreateCredentialCallback {
- void onSuccess(in CreateCredentialResponse request);
- void onFailure(int errorCode, in CharSequence message);
-}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index c21cefa..b9eb3ed 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -18,9 +18,9 @@
import android.os.ICancellationSignal;
import android.service.credentials.GetCredentialsRequest;
-import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.IGetCredentialsCallback;
-import android.service.credentials.ICreateCredentialCallback;
+import android.service.credentials.IBeginCreateCredentialCallback;
import android.os.ICancellationSignal;
/**
@@ -30,5 +30,5 @@
*/
interface ICredentialProviderService {
ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
- ICancellationSignal onCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
+ ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 09d0fc5..e5c9adb 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
+import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
@@ -725,7 +726,7 @@
*/
@Deprecated
@RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
- public void onCallStateChanged(@Annotation.CallState int state, String phoneNumber) {
+ public void onCallStateChanged(@CallState int state, String phoneNumber) {
// default implementation empty
}
@@ -1568,48 +1569,12 @@
() -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
}
- public void onCallStatesChanged(List<CallState> callStateList) {
+ public void onCallAttributesChanged(CallAttributes callAttributes) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
- if (callStateList == null) return;
- CallAttributes ca;
- if (callStateList.isEmpty()) {
- ca = new CallAttributes(
- new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
- } else {
- int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- for (CallState cs : callStateList) {
- switch (cs.getCallClassification()) {
- case CallState.CALL_CLASSIFICATION_FOREGROUND:
- foregroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_BACKGROUND:
- backgroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_RINGING:
- ringingCallState = cs.getCallState();
- break;
- default:
- break;
- }
- }
- ca = new CallAttributes(
- new PreciseCallState(
- foregroundCallState, backgroundCallState, ringingCallState,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- callStateList.get(0).getNetworkType(),
- callStateList.get(0).getCallQuality());
- }
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(
- () -> psl.onCallAttributesChanged(ca)));
+ () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 54c4b668..e8960b8 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,7 +27,6 @@
import android.os.Build;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
@@ -63,7 +62,7 @@
* appropriate sub-interfaces.
*/
public class TelephonyCallback {
- private static final String LOG_TAG = "TelephonyCallback";
+
/**
* Experiment flag to set the per-pid registration limit for TelephonyCallback
*
@@ -1333,9 +1332,7 @@
@SystemApi
public interface CallAttributesListener {
/**
- * Callback invoked when the call attributes changes on the active call on the registered
- * subscription. If the user swaps between a foreground and background call the call
- * attributes will be reported for the active call only.
+ * Callback invoked when the call attributes changes on the registered subscription.
* Note, the registration subscription ID comes from {@link TelephonyManager} object
* which registers TelephonyCallback by
* {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
@@ -1349,77 +1346,9 @@
* {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
*
* @param callAttributes the call attributes
- * @deprecated Use onCallStatesChanged({@link List<CallState>}) to get each of call
- * state for all ongoing calls on the subscription.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- @Deprecated
- default void onCallAttributesChanged(@NonNull CallAttributes callAttributes) {
- Log.w(LOG_TAG, "onCallAttributesChanged(List<CallAttributes>) should be "
- + "overridden.");
- }
-
- /**
- * Callback invoked when the call attributes changes on the ongoing calls on the registered
- * subscription. If there are 1 foreground and 1 background call, Two {@link CallState}
- * will be passed.
- * Note, the registration subscription ID comes from {@link TelephonyManager} object
- * which registers TelephonyCallback by
- * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subscription ID. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- * In the event that there are no active(state is not
- * {@link PreciseCallState#PRECISE_CALL_STATE_IDLE}) calls, this API will report empty list.
- *
- * The calling app should have carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
- * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
- *
- * @param callStateList the list of call states for each ongoing call. If there are
- * a active call and a holding call, 1 call attributes for
- * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} and another
- * for {@link PreciseCallState#PRECISE_CALL_STATE_HOLDING}
- * will be in this list.
- */
- // Added as default for backward compatibility
- @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- default void onCallStatesChanged(@NonNull List<CallState> callStateList) {
- if (callStateList.size() > 0) {
- int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- for (CallState cs : callStateList) {
- switch (cs.getCallClassification()) {
- case CallState.CALL_CLASSIFICATION_FOREGROUND:
- foregroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_BACKGROUND:
- backgroundCallState = cs.getCallState();
- break;
- case CallState.CALL_CLASSIFICATION_RINGING:
- ringingCallState = cs.getCallState();
- break;
- default:
- break;
- }
- }
- onCallAttributesChanged(new CallAttributes(
- new PreciseCallState(
- ringingCallState, foregroundCallState, backgroundCallState,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- callStateList.get(0).getNetworkType(),
- callStateList.get(0).getCallQuality()));
- } else {
- onCallAttributesChanged(new CallAttributes(
- new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- PreciseCallState.PRECISE_CALL_STATE_IDLE,
- DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()));
- }
- }
+ void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
}
/**
@@ -1773,13 +1702,14 @@
() -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state)));
}
- public void onCallStatesChanged(List<CallState> callStateList) {
+ public void onCallAttributesChanged(CallAttributes callAttributes) {
CallAttributesListener listener =
(CallAttributesListener) mTelephonyCallbackWeakRef.get();
if (listener == null) return;
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> listener.onCallStatesChanged(callStateList)));
+ () -> mExecutor.execute(() -> listener.onCallAttributesChanged(
+ callAttributes)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a1538de..a3696e3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -32,13 +32,13 @@
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
import android.telephony.emergency.EmergencyNumber;
-import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.util.ArraySet;
import android.util.Log;
@@ -741,20 +741,17 @@
* @param slotIndex for which precise call state changed. Can be derived from subId except when
* subId is invalid.
* @param subId for which precise call state changed.
- * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
- * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for
- * ringing, foreground & background calls.
- * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
- * background calls.
- * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+ * @param ringCallPreciseState ringCall state.
+ * @param foregroundCallPreciseState foreground call state.
+ * @param backgroundCallPreciseState background call state.
*/
public void notifyPreciseCallState(int slotIndex, int subId,
- @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
- @Annotation.ImsCallServiceType int[] imsServiceTypes,
- @Annotation.ImsCallType int[] imsCallTypes) {
+ @PreciseCallStates int ringCallPreciseState,
+ @PreciseCallStates int foregroundCallPreciseState,
+ @PreciseCallStates int backgroundCallPreciseState) {
try {
- sRegistry.notifyPreciseCallState(slotIndex, subId, callStates,
- imsCallIds, imsServiceTypes, imsCallTypes);
+ sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState,
+ foregroundCallPreciseState, backgroundCallPreciseState);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ef18458..57b2d39 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -728,8 +728,8 @@
private void releaseSurfaces(boolean releaseSurfacePackage) {
mAlpha = 1f;
+ mSurface.destroy();
synchronized (mSurfaceControlLock) {
- mSurface.destroy();
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index d77e499..03b25c2 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,6 +29,7 @@
import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.indexOf;
import static android.view.WindowInsets.Type.systemBars;
@@ -597,7 +598,10 @@
return new WindowInsets(null, null,
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
- displayCutoutCopyConstructorArgument(this),
+ // If the system window insets types contain displayCutout, we should also consume
+ // it.
+ (mCompatInsetsTypes & displayCutout()) != 0
+ ? null : displayCutoutCopyConstructorArgument(this),
mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
mCompatIgnoreVisibility);
}
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 85f5056..44b6deb 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -20,13 +20,18 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ResolveInfo;
import android.graphics.Region;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
@@ -34,14 +39,15 @@
import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to
* interact with the windows in the display that this proxy represents. Proxying the default display
- * or a display that is not tracked will throw an exception. Only the real user has access to global
- * clients like SystemUI.
+ * or a display that is not tracked by accessibility, such as private displays, will throw an
+ * exception. Only the real user has access to global clients like SystemUI.
*
* <p>
* To register and unregister a proxy, use
@@ -49,7 +55,16 @@
* and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app
* that has registered the proxy dies, the system will remove the proxy.
*
- * TODO(241429275): Complete proxy impl and add additional support (if necessary) like cache methods
+ * <p>
+ * Avoid using the app's main thread. Proxy methods such as {@link #getWindows} and node methods
+ * like {@link AccessibilityNodeInfo#getChild(int)} will happen frequently. Node methods may also
+ * wait on the displayed app's UI thread to obtain accurate screen data.
+ *
+ * <p>
+ * To get a list of {@link AccessibilityServiceInfo}s that have populated {@link ComponentName}s and
+ * {@link ResolveInfo}s, retrieve the list using {@link #getInstalledAndEnabledServices()} after
+ * {@link #onProxyConnected()} has been called.
+ *
* @hide
*/
@SystemApi
@@ -91,7 +106,134 @@
}
/**
- * An IAccessibilityServiceClient that handles interrupts and accessibility events.
+ * Handles {@link android.view.accessibility.AccessibilityEvent}s.
+ * <p>
+ * AccessibilityEvents represent changes to the UI, or what parts of the node tree have changed.
+ * AccessibilityDisplayProxy should use these to query new UI and send appropriate feedback
+ * to their users.
+ * <p>
+ * For example, a {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} indicates a change in windows,
+ * so a proxy may query {@link #getWindows} to obtain updated UI and potentially inform of a new
+ * window title. Or a proxy may emit an earcon on a
+ * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+ */
+ public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+ // Default no-op
+ }
+
+ /**
+ * Handles a successful system connection after
+ * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} is called.
+ *
+ * <p>
+ * At this point, querying for UI is available and {@link AccessibilityEvent}s will begin being
+ * sent. An AccessibilityDisplayProxy may instantiate core infrastructure components here.
+ */
+ public void onProxyConnected() {
+ // Default no-op
+ }
+
+ /**
+ * Handles a request to interrupt the accessibility feedback.
+ * <p>
+ * AccessibilityDisplayProxy should interrupt the accessibility activity occurring on its
+ * display. For example, a screen reader may interrupt speech.
+ *
+ * @see AccessibilityManager#interrupt()
+ * @see AccessibilityService#onInterrupt()
+ */
+ public void interrupt() {
+ // Default no-op
+ }
+
+ /**
+ * Gets the focus of the window specified by {@code windowInfo}.
+ *
+ * @param windowInfo the window to search
+ * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ * @hide
+ * TODO(254545943): Do not expose until support for accessibility focus and/or input is in place
+ */
+ @Nullable
+ public AccessibilityNodeInfo findFocus(@NonNull AccessibilityWindowInfo windowInfo, int focus) {
+ AccessibilityNodeInfo windowRoot = windowInfo.getRoot();
+ return windowRoot != null ? windowRoot.findFocus(focus) : null;
+ }
+
+ /**
+ * Gets the windows of the tracked display.
+ *
+ * @see AccessibilityService#getWindows()
+ */
+ @NonNull
+ public List<AccessibilityWindowInfo> getWindows() {
+ return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(mConnectionId,
+ mDisplayId);
+ }
+
+ /**
+ * Sets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+ * {@link AccessibilityDisplayProxy}'s display.
+ *
+ * <p>These represent a11y features and services that are installed and running. These should
+ * not include {@link AccessibilityService}s installed on the phone.
+ *
+ * @param installedAndEnabledServices the list of installed and running a11y services.
+ */
+ public void setInstalledAndEnabledServices(
+ @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) {
+ mInstalledAndEnabledServices = installedAndEnabledServices;
+ sendServiceInfos();
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+ * properly set and there is an {@link IAccessibilityServiceConnection} to the
+ * AccessibilityManagerService.
+ */
+ private void sendServiceInfos() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (mInstalledAndEnabledServices != null && mInstalledAndEnabledServices.size() > 0
+ && connection != null) {
+ try {
+ connection.setInstalledAndEnabledServices(mInstalledAndEnabledServices);
+ AccessibilityInteractionClient.getInstance().clearCache(mConnectionId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfos", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ mInstalledAndEnabledServices = null;
+ }
+
+ /**
+ * Gets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
+ * {@link AccessibilityDisplayProxy}'s display.
+ *
+ * @return The {@link AccessibilityServiceInfo}s of interested services.
+ * @see AccessibilityServiceInfo
+ */
+ @NonNull
+ public final List<AccessibilityServiceInfo> getInstalledAndEnabledServices() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getInstalledAndEnabledServices();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * An IAccessibilityServiceClient that handles interrupts, accessibility events, and system
+ * connection.
*/
private class IAccessibilityServiceClientImpl extends
AccessibilityService.IAccessibilityServiceClientWrapper {
@@ -100,17 +242,24 @@
super(context, executor, new AccessibilityService.Callbacks() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
- // TODO: call AccessiiblityProxy.onAccessibilityEvent
+ // TODO(254545943): Remove check when event processing is done more upstream in
+ // AccessibilityManagerService.
+ if (event.getDisplayId() == mDisplayId) {
+ AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
+ }
}
@Override
public void onInterrupt() {
- // TODO: call AccessiiblityProxy.onInterrupt
+ AccessibilityDisplayProxy.this.interrupt();
}
+
@Override
public void onServiceConnected() {
- // TODO: send service infos and call AccessiiblityProxy.onProxyConnected
+ AccessibilityDisplayProxy.this.sendServiceInfos();
+ AccessibilityDisplayProxy.this.onProxyConnected();
}
+
@Override
public void init(int connectionId, IBinder windowToken) {
mConnectionId = connectionId;
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 7030ab5..06a6de9 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -458,15 +458,21 @@
* @return The {@link AccessibilityWindowInfo} list.
*/
public List<AccessibilityWindowInfo> getWindows(int connectionId) {
- final SparseArray<List<AccessibilityWindowInfo>> windows =
- getWindowsOnAllDisplays(connectionId);
- if (windows.size() > 0) {
- return windows.valueAt(Display.DEFAULT_DISPLAY);
- }
- return Collections.emptyList();
+ return getWindowsOnDisplay(connectionId, Display.DEFAULT_DISPLAY);
}
/**
+ * Gets the info for all windows of the specified display.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @return The {@link AccessibilityWindowInfo} list belonging to {@code displayId}.
+ */
+ public List<AccessibilityWindowInfo> getWindowsOnDisplay(int connectionId, int displayId) {
+ final SparseArray<List<AccessibilityWindowInfo>> windows =
+ getWindowsOnAllDisplays(connectionId);
+ return windows.get(displayId, Collections.emptyList());
+ }
+ /**
* Gets the info for all windows of all displays.
*
* @param connectionId The id of a connection for interacting with the system.
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index fdc3e5a..f2ae973 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -146,7 +146,7 @@
@SuppressLint("OnNameExpected")
@Override
- public void onConfigurationChanged(@Nullable Configuration configuration) {
+ public void onConfigurationChanged(@NonNull Configuration configuration) {
// This is only called from WindowTokenClient.
mCallbacksController.dispatchConfigurationChanged(configuration);
}
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index 76e068d..6ceccd1 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -17,14 +17,20 @@
package com.android.internal.content.om;
import static android.content.Context.MODE_PRIVATE;
+import static android.content.om.OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY;
import android.annotation.NonNull;
+import android.annotation.NonUiContext;
import android.content.Context;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
@@ -48,6 +54,7 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -129,10 +136,9 @@
}
}
- /**
- * Ensure the base dir for self-targeting is valid.
- */
+ /** Ensure the base dir for self-targeting is valid. */
@VisibleForTesting
+ @NonUiContext
public void ensureBaseDir() {
final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath();
final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName();
@@ -170,6 +176,16 @@
mBasePath = baseFile.toPath();
}
+ private boolean isSameWithTargetSignature(final String targetPackage) {
+ final PackageManager packageManager = mContext.getPackageManager();
+ final String packageName = mContext.getPackageName();
+ if (TextUtils.equals(packageName, targetPackage)) {
+ return true;
+ }
+ return packageManager.checkSignatures(packageName, targetPackage)
+ == PackageManager.SIGNATURE_MATCH;
+ }
+
/**
* Check if the overlay name is valid or not.
*
@@ -202,8 +218,12 @@
/**
* Save FabricatedOverlay instance as frro and idmap files.
*
+ * <p>In order to fill the overlayable policy, it's necessary to collect the information from
+ * app. And then, the information is passed to native layer to fill the overlayable policy
+ *
* @param overlayInternal the FabricatedOverlayInternal to be saved.
*/
+ @NonUiContext
public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal)
throws IOException, PackageManager.NameNotFoundException {
ensureBaseDir();
@@ -214,6 +234,9 @@
final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
checkPackageName(overlayInternal.packageName);
checkPackageName(overlayInternal.targetPackageName);
+ Preconditions.checkStringNotEmpty(
+ overlayInternal.targetOverlayable,
+ "Target overlayable should be neither null nor empty string.");
final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
final String targetPackage = Preconditions.checkStringNotEmpty(
@@ -223,7 +246,17 @@
createFrroFile(frroPath.toString(), overlayInternal);
try {
- createIdmapFile(targetPackage, frroPath.toString(), idmapPath.toString(), overlayName);
+ createIdmapFile(
+ targetPackage,
+ frroPath.toString(),
+ idmapPath.toString(),
+ overlayName,
+ applicationInfo.isSystemApp() || applicationInfo.isSystemExt() /* isSystem */,
+ applicationInfo.isVendor(),
+ applicationInfo.isProduct(),
+ isSameWithTargetSignature(overlayInternal.targetPackageName),
+ applicationInfo.isOdm(),
+ applicationInfo.isOem());
} catch (IOException e) {
if (!frroPath.toFile().delete()) {
Log.w(TAG, "Failed to delete file " + frroPath);
@@ -237,6 +270,7 @@
*
* @param overlayName the specific name
*/
+ @NonUiContext
public void unregisterFabricatedOverlay(@NonNull String overlayName) {
ensureBaseDir();
checkOverlayNameValid(overlayName);
@@ -252,6 +286,46 @@
}
/**
+ * Commit the overlay manager transaction
+ *
+ * @param transaction the overlay manager transaction
+ */
+ @NonUiContext
+ public void commit(@NonNull OverlayManagerTransaction transaction)
+ throws PackageManager.NameNotFoundException, IOException {
+ Objects.requireNonNull(transaction);
+
+ for (Iterator<OverlayManagerTransaction.Request> it = transaction.iterator();
+ it.hasNext(); ) {
+ final OverlayManagerTransaction.Request request = it.next();
+ if (request.type == TYPE_REGISTER_FABRICATED) {
+ final FabricatedOverlayInternal fabricatedOverlayInternal =
+ Objects.requireNonNull(
+ request.extras.getParcelable(
+ BUNDLE_FABRICATED_OVERLAY,
+ FabricatedOverlayInternal.class));
+
+ // populate the mandatory data
+ if (TextUtils.isEmpty(fabricatedOverlayInternal.packageName)) {
+ fabricatedOverlayInternal.packageName = mContext.getPackageName();
+ } else {
+ if (!TextUtils.equals(
+ fabricatedOverlayInternal.packageName, mContext.getPackageName())) {
+ throw new IllegalArgumentException("Unknown package name in transaction");
+ }
+ }
+
+ registerFabricatedOverlay(fabricatedOverlayInternal);
+ } else if (request.type == TYPE_UNREGISTER_FABRICATED) {
+ final OverlayIdentifier overlayIdentifier = Objects.requireNonNull(request.overlay);
+ unregisterFabricatedOverlay(overlayIdentifier.getOverlayName());
+ } else {
+ throw new IllegalArgumentException("Unknown request in transaction " + request);
+ }
+ }
+ }
+
+ /**
* Get the list of overlays information for the target package name.
*
* @param targetPackage the target package name
@@ -315,7 +389,13 @@
@NonNull String targetPath,
@NonNull String overlayPath,
@NonNull String idmapPath,
- @NonNull String overlayName)
+ @NonNull String overlayName,
+ boolean isSystem,
+ boolean isVendor,
+ boolean isProduct,
+ boolean isSameWithTargetSignature,
+ boolean isOdm,
+ boolean isOem)
throws IOException;
private static native FabricatedOverlayInfo getFabricatedOverlayInfo(
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index 680f8fe..a587834 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -40,7 +40,8 @@
TimeoutKind.SERVICE_START,
TimeoutKind.SERVICE_EXEC,
TimeoutKind.CONTENT_PROVIDER,
- TimeoutKind.APP_REGISTERED})
+ TimeoutKind.APP_REGISTERED,
+ TimeoutKind.SHORT_FGS_TIMEOUT})
@Retention(RetentionPolicy.SOURCE)
public @interface TimeoutKind {
@@ -51,6 +52,7 @@
int SERVICE_EXEC = 5;
int CONTENT_PROVIDER = 6;
int APP_REGISTERED = 7;
+ int SHORT_FGS_TIMEOUT = 8;
}
/** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -144,4 +146,10 @@
public static TimeoutRecord forApp(@NonNull String reason) {
return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason);
}
+
+ /** Record for a "short foreground service" timeout. */
+ @NonNull
+ public static TimeoutRecord forShortFgsTimeout(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
+ }
}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 9cb2e68..4b1753a 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -17,7 +17,7 @@
package com.android.internal.telephony;
import android.telephony.BarringInfo;
-import android.telephony.CallState;
+import android.telephony.CallAttributes;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
@@ -62,7 +62,7 @@
void onPhoneCapabilityChanged(in PhoneCapability capability);
void onActiveDataSubIdChanged(in int subId);
void onRadioPowerStateChanged(in int state);
- void onCallStatesChanged(in List<CallState> callStateList);
+ void onCallAttributesChanged(in CallAttributes callAttributes);
@SuppressWarnings(value={"untyped-collection"})
void onEmergencyNumberListChanged(in Map emergencyNumberList);
void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 7ba2686..c7fa757 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -66,8 +66,8 @@
void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
- void notifyPreciseCallState(int phoneId, int subId, in int[] callStates, in String[] imsCallIds,
- in int[] imsCallServiceTypes, in int[] imsCallTypes);
+ void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
+ int foregroundCallState, int backgroundCallState);
void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
int preciseDisconnectCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index df55e42..bba1760 100644
--- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -123,8 +123,12 @@
bool callCreateIdmapFile(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath, const std::string& idmapPath,
- const std::string& overlayName) {
- return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName);
+ const std::string& overlayName, const bool isSystem,
+ const bool isVendor, const bool isProduct,
+ const bool isTargetSignature, const bool isOdm, const bool isOem) {
+ return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName,
+ isSystem, isVendor, isProduct, isTargetSignature, isOdm,
+ isOem);
}
bool callGetFabricatedOverlayInfo(std::string& out_error, const std::string& overlay_path,
@@ -158,7 +162,10 @@
typedef bool (*CreateIdmapFileFunc)(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath,
const std::string& idmapPath,
- const std::string& overlayName);
+ const std::string& overlayName, const jboolean isSystem,
+ const jboolean isVendor, const jboolean isProduct,
+ const jboolean isSameWithTargetSignature,
+ const jboolean isOdm, const jboolean isOem);
typedef bool (*GetFabricatedOverlayInfoFunc)(std::string& out_error,
const std::string& overlay_path,
@@ -295,7 +302,9 @@
}
static void CreateIdmapFile(JNIEnv* env, jclass /* clazz */, jstring jsTargetPath,
- jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName) {
+ jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName,
+ jboolean isSystem, jboolean isVendor, jboolean isProduct,
+ jboolean isTargetSignature, jboolean isOdm, jboolean isOem) {
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
jniThrowNullPointerException(env, "libidmap2 is not loaded");
@@ -327,7 +336,10 @@
std::string err_result;
if (!dlLoader.callCreateIdmapFile(err_result, targetPath.c_str(), overlayPath.c_str(),
- idmapPath.c_str(), overlayName.c_str())) {
+ idmapPath.c_str(), overlayName.c_str(),
+ (isSystem == JNI_TRUE), (isVendor == JNI_TRUE),
+ (isProduct == JNI_TRUE), (isTargetSignature == JNI_TRUE),
+ (isOdm == JNI_TRUE), (isOem == JNI_TRUE))) {
jniThrowException(env, kIOException, err_result.c_str());
return;
}
@@ -374,7 +386,7 @@
{"createFrroFile", "(Ljava/lang/String;Landroid/os/FabricatedOverlayInternal;)V",
reinterpret_cast<void*>(self_targeting::CreateFrroFile)},
{"createIdmapFile",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZZZ)V",
reinterpret_cast<void*>(self_targeting::CreateIdmapFile)},
{"getFabricatedOverlayInfo", "(Ljava/lang/String;)Landroid/os/FabricatedOverlayInfo;",
reinterpret_cast<void*>(self_targeting::GetFabricatedOverlayInfo)},
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 4d5b0d2..561c10ba 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -21,7 +21,8 @@
import static org.mockito.Mockito.when;
import android.app.backup.BackupAgent.IncludeExcludeRules;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
@@ -66,7 +67,7 @@
excludePaths.add(path);
IncludeExcludeRules expectedRules = new IncludeExcludeRules(includePaths, excludePaths);
- mBackupAgent = getAgentForOperationType(OperationType.BACKUP);
+ mBackupAgent = getAgentForBackupDestination(BackupDestination.CLOUD);
when(mBackupScheme.maybeParseAndGetCanonicalExcludePaths()).thenReturn(excludePaths);
when(mBackupScheme.maybeParseAndGetCanonicalIncludePaths()).thenReturn(includePaths);
@@ -84,24 +85,26 @@
@Test
public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() {
BackupAgent agent = new TestFullBackupAgent();
- agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+ agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.BACKUP);
- assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(
+ OperationType.BACKUP);
}
@Test
public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() {
BackupAgent agent = new TestFullBackupAgent();
- agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+ agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.RESTORE);
- assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(
+ OperationType.RESTORE);
}
@Test
public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent()
throws Exception {
BackupAgent agent = new TestFullBackupAgent();
- agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+ agent.onCreate(USER_HANDLE, BackupDestination.CLOUD, OperationType.BACKUP);
// TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called.
agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0));
@@ -110,9 +113,9 @@
.isEqualTo(DATA_TYPE_BACKED_UP);
}
- private BackupAgent getAgentForOperationType(@OperationType int operationType) {
+ private BackupAgent getAgentForBackupDestination(@BackupDestination int backupDestination) {
BackupAgent agent = new TestFullBackupAgent();
- agent.onCreate(USER_HANDLE, operationType);
+ agent.onCreate(USER_HANDLE, backupDestination);
return agent;
}
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index b9fdc6d..112d394 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -16,8 +16,8 @@
package android.app.backup;
-import static android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
-import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
+import static android.app.backup.BackupAnnotations.OperationType.BACKUP;
+import static android.app.backup.BackupAnnotations.OperationType.RESTORE;
import static com.google.common.truth.Truth.assertThat;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index ee1e10f..7cbf3ffa 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -263,10 +263,24 @@
}
private class MyAccessibilityProxy extends AccessibilityDisplayProxy {
- // TODO(241429275): Will override A11yProxy methods in the future.
MyAccessibilityProxy(int displayId,
@NonNull List<AccessibilityServiceInfo> serviceInfos) {
super(displayId, Executors.newSingleThreadExecutor(), serviceInfos);
}
+
+ @Override
+ public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
+
+ }
+
+ @Override
+ public void onProxyConnected() {
+
+ }
+
+ @Override
+ public void interrupt() {
+
+ }
}
}
diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp
index 82998db..063c569 100644
--- a/core/tests/overlaytests/device_self_targeting/Android.bp
+++ b/core/tests/overlaytests/device_self_targeting/Android.bp
@@ -29,6 +29,7 @@
"androidx.test.rules",
"androidx.test.runner",
"androidx.test.ext.junit",
+ "mockito-target-minus-junit4",
"truth-prebuilt",
],
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
new file mode 100644
index 0000000..5cc214d
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/values/overlayable.xml
@@ -0,0 +1,84 @@
+<?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>
+ <overlayable name="PublicOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="public">
+ <item type="color" name="public_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="SignatureOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="signature">
+ <item type="color" name="mycolor" />
+ <item type="color" name="signature_overlayable_color" />
+ <item type="string" name="mystring" />
+ <item type="drawable" name="mydrawable" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="SystemAppOverlayable" actor="overlay://theme">
+ <!-- The app in system partition can overlay the below resources -->
+ <policy type="system">
+ <item type="color" name="system_app_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="OdmOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="odm">
+ <item type="color" name="odm_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="OemOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="oem">
+ <item type="color" name="oem_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="VendorOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="vendor">
+ <item type="color" name="vendor_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="ProductOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="product">
+ <item type="color" name="product_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="ActorOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="actor">
+ <item type="color" name="actor_overlayable_color" />
+ </policy>
+ </overlayable>
+
+ <overlayable name="ConfigOverlayable" actor="overlay://theme">
+ <!-- The app with the same signature can overlay the below resources -->
+ <policy type="config_signature">
+ <item type="color" name="config_overlayable_color" />
+ </policy>
+ </overlayable>
+
+</resources>
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/values.xml b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
index f0b4a6f..d82de97 100644
--- a/core/tests/overlaytests/device_self_targeting/res/values/values.xml
+++ b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
@@ -17,4 +17,14 @@
<resources>
<color name="mycolor">#ff112233</color>
<string name="mystring">hello</string>
+
+ <color name="public_overlayable_color">#000</color>
+ <color name="signature_overlayable_color">#000</color>
+ <color name="system_app_overlayable_color">#000</color>
+ <color name="odm_overlayable_color">#000</color>
+ <color name="oem_overlayable_color">#000</color>
+ <color name="vendor_overlayable_color">#000</color>
+ <color name="product_overlayable_color">#000</color>
+ <color name="actor_overlayable_color">#000</color>
+ <color name="config_overlayable_color">#000</color>
</resources>
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index ca58410..40d0bef 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -17,15 +17,24 @@
package com.android.overlaytest;
import static android.content.Context.MODE_PRIVATE;
+import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
import static com.android.internal.content.om.OverlayManagerImpl.SELF_TARGET;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.om.FabricatedOverlay;
import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.FabricatedOverlayInternal;
@@ -72,11 +81,23 @@
private static final String TARGET_COLOR_RES = "color/mycolor";
private static final String TARGET_STRING_RES = "string/mystring";
private static final String TARGET_DRAWABLE_RES = "drawable/mydrawable";
+ private static final String PUBLIC_OVERLAYABLE = "PublicOverlayable";
+ private static final String SIGNATURE_OVERLAYABLE = "SignatureOverlayable";
+ private static final String SYSTEM_APP_OVERLAYABLE = "SystemAppOverlayable";
+ private static final String ODM_OVERLAYABLE = "OdmOverlayable";
+ private static final String OEM_OVERLAYABLE = "OemOverlayable";
+ private static final String VENDOR_OVERLAYABLE = "VendorOverlayable";
+ private static final String PRODUCT_OVERLAYABLE = "ProductOverlayable";
+ private static final String ACTOR_OVERLAYABLE = "ActorOverlayable";
+ private static final String CONFIG_OVERLAYABLE = "ConfigOverlayable";
private Context mContext;
private OverlayManagerImpl mOverlayManagerImpl;
private String mOverlayName;
+ private PackageManager mMockPackageManager;
+ private ApplicationInfo mMockApplicationInfo;
+
@Rule public TestName mTestName = new TestName();
@Rule public Expect expect = Expect.create();
@@ -111,7 +132,36 @@
public void setUp() throws IOException {
clearDir();
mOverlayName = mTestName.getMethodName();
- mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mMockApplicationInfo = mock(ApplicationInfo.class);
+ when(mMockApplicationInfo.isSystemApp()).thenReturn(false);
+ when(mMockApplicationInfo.isSystemExt()).thenReturn(false);
+ when(mMockApplicationInfo.isOdm()).thenReturn(false);
+ when(mMockApplicationInfo.isOem()).thenReturn(false);
+ when(mMockApplicationInfo.isVendor()).thenReturn(false);
+ when(mMockApplicationInfo.isProduct()).thenReturn(false);
+ when(mMockApplicationInfo.getBaseCodePath()).thenReturn(
+ context.getApplicationInfo().getBaseCodePath());
+ mMockApplicationInfo.sourceDir = context.getApplicationInfo().sourceDir;
+
+ mMockPackageManager = mock(PackageManager.class);
+ when(mMockPackageManager.checkSignatures(anyString(), anyString()))
+ .thenReturn(SIGNATURE_NO_MATCH);
+
+ mContext =
+ new ContextWrapper(context) {
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return mMockApplicationInfo;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
+ };
+
mOverlayManagerImpl = new OverlayManagerImpl(mContext);
}
@@ -144,12 +194,14 @@
private <T> FabricatedOverlayInternal createOverlayWithName(
@NonNull String overlayName,
+ @NonNull String targetOverlayable,
@NonNull String targetPackageName,
@NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) {
final String packageName = mContext.getPackageName();
FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal();
overlayInternal.overlayName = overlayName;
overlayInternal.targetPackageName = targetPackageName;
+ overlayInternal.targetOverlayable = targetOverlayable;
overlayInternal.packageName = packageName;
addOverlayEntry(overlayInternal, entryDefinitions);
@@ -162,6 +214,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SYSTEM_APP_OVERLAYABLE,
"android",
List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
@@ -190,6 +243,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
@@ -215,6 +269,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_STRING_RES, Pair.create(null, "HELLO"))));
@@ -242,6 +297,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_DRAWABLE_RES,
Pair.create(null, parcelFileDescriptor))));
@@ -268,6 +324,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create("color/not_existed", Pair.create(null, "HELLO"))));
@@ -301,6 +358,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -309,6 +367,7 @@
overlayInternal =
createOverlayWithName(
secondOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -341,6 +400,7 @@
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -349,6 +409,7 @@
overlayInternal =
createOverlayWithName(
mOverlayName,
+ SIGNATURE_OVERLAYABLE,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
@@ -392,6 +453,40 @@
}
@Test
+ public void commit_withNullTransaction_shouldFail() {
+ assertThrows(NullPointerException.class, () -> mOverlayManagerImpl.commit(null));
+ }
+
+ @Test
+ public void commitRegisterOverlay_fromOtherBuilder_shouldWork()
+ throws PackageManager.NameNotFoundException, IOException {
+ FabricatedOverlay overlay =
+ new FabricatedOverlay.Builder(
+ mContext.getPackageName(), mOverlayName, mContext.getPackageName())
+ .setTargetOverlayable(SIGNATURE_OVERLAYABLE)
+ .setResourceValue(
+ TARGET_COLOR_RES, TypedValue.TYPE_INT_COLOR_ARGB8, Color.WHITE)
+ .build();
+ OverlayManagerTransaction transaction =
+ new OverlayManagerTransaction.Builder().registerFabricatedOverlay(overlay).build();
+
+ mOverlayManagerImpl.commit(transaction);
+
+ final List<OverlayInfo> overlayInfos =
+ mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
+ final int firstNumberOfOverlays = overlayInfos.size();
+ expect.that(firstNumberOfOverlays).isEqualTo(1);
+ final OverlayInfo overlayInfo = overlayInfos.get(0);
+ expect.that(overlayInfo).isNotNull();
+ Truth.assertThat(expect.hasFailures()).isFalse();
+ expect.that(overlayInfo.isFabricated()).isTrue();
+ expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
+ expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
+ expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
+ expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
+ }
+
+ @Test
public void newOverlayManagerImpl_forOtherUser_shouldFail() {
Context fakeContext =
new ContextWrapper(mContext) {
@@ -408,4 +503,177 @@
assertThrows(SecurityException.class, () -> new OverlayManagerImpl(fakeContext));
}
+
+ FabricatedOverlayInternal prepareFabricatedOverlayInternal(
+ String targetOverlayableName, String targetEntryName) {
+ return createOverlayWithName(
+ mOverlayName,
+ targetOverlayableName,
+ mContext.getPackageName(),
+ List.of(
+ Pair.create(
+ targetEntryName,
+ Pair.create(null, Color.WHITE))));
+ }
+
+ @Test
+ public void registerOverlayOnSystemOverlayable_selfIsNotSystemApp_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ SYSTEM_APP_OVERLAYABLE,
+ "color/system_app_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnOdmOverlayable_selfIsNotOdm_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ ODM_OVERLAYABLE,
+ "color/odm_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnOemOverlayable_selfIsNotOem_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ OEM_OVERLAYABLE,
+ "color/oem_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnVendorOverlayable_selfIsNotVendor_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ VENDOR_OVERLAYABLE,
+ "color/vendor_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnProductOverlayable_selfIsNotProduct_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ PRODUCT_OVERLAYABLE,
+ "color/product_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnActorOverlayable_notSupport_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ ACTOR_OVERLAYABLE,
+ "color/actor_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnConfigOverlayable_notSupport_shouldFail() {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ CONFIG_OVERLAYABLE,
+ "color/config_overlayable_color");
+
+ assertThrows(
+ IOException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
+ public void registerOverlayOnPublicOverlayable_shouldAlwaysSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ PUBLIC_OVERLAYABLE,
+ "color/public_overlayable_color");
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnSystemOverlayable_selfIsSystemApp_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ SYSTEM_APP_OVERLAYABLE,
+ "color/system_app_overlayable_color");
+ when(mMockApplicationInfo.isSystemApp()).thenReturn(true);
+ when(mMockApplicationInfo.isSystemExt()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnOdmOverlayable_selfIsOdm_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ ODM_OVERLAYABLE,
+ "color/odm_overlayable_color");
+ when(mMockApplicationInfo.isOdm()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnOemOverlayable_selfIsOem_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ OEM_OVERLAYABLE,
+ "color/oem_overlayable_color");
+ when(mMockApplicationInfo.isOem()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnVendorOverlayable_selfIsVendor_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ VENDOR_OVERLAYABLE,
+ "color/vendor_overlayable_color");
+ when(mMockApplicationInfo.isVendor()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void registerOverlayOnProductOverlayable_selfIsProduct_shouldSucceed()
+ throws PackageManager.NameNotFoundException, IOException {
+ final FabricatedOverlayInternal overlayInternal = prepareFabricatedOverlayInternal(
+ PRODUCT_OVERLAYABLE,
+ "color/product_overlayable_color");
+ when(mMockApplicationInfo.isProduct()).thenReturn(true);
+
+ mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
+
+ assertThat(mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size())
+ .isEqualTo(1);
+ }
}
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
new file mode 100644
index 0000000..b27c5e0
--- /dev/null
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -0,0 +1,194 @@
+/*
+ * 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.graphics;
+
+import android.annotation.IntDef;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * 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 alpha type.
+ *
+ * Note that there are several limitations on various mesh specifications:
+ * 1. The max amount of attributes allowed is 8.
+ * 2. The offset alignment length is 4 bytes.
+ * 2. The max stride length is 1024.
+ * 3. The max amount of varyings is 6.
+ *
+ * These should be kept in mind when generating a mesh specification, as exceeding them will
+ * lead to errors.
+ *
+ * @hide
+ */
+public class MeshSpecification {
+ private long mNativeMeshSpec;
+
+ /**
+ * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+ * to determine alpha type
+ */
+ @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
+ public @interface AlphaType {
+ }
+
+ public static final int UNKNOWN = 0;
+ public static final int OPAQUE = 1;
+ public static final int PREMUL = 2;
+ public static final int UNPREMULT = 3;
+
+ /**
+ * Constants for {@link Attribute} and {@link Varying} for determining the data type.
+ */
+ @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
+ public @interface Type {
+ }
+
+ public static final int FLOAT = 0;
+ public static final int FLOAT2 = 1;
+ public static final int FLOAT3 = 2;
+ public static final int FLOAT4 = 3;
+ public static final int UBYTE4 = 4;
+
+ /**
+ * Data class to represent a single attribute in a shader. Note that type parameter must be
+ * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+ */
+ public static class Attribute {
+ @Type
+ private int mType;
+ private int mOffset;
+ private String mName;
+
+ public Attribute(@Type int type, int offset, String name) {
+ mType = type;
+ mOffset = offset;
+ mName = name;
+ }
+ }
+
+ /**
+ * Data class to represent a single varying variable. Note that type parameter must be
+ * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+ */
+ public static class Varying {
+ @Type
+ private int mType;
+ private String mName;
+
+ public Varying(@Type int type, String name) {
+ mType = type;
+ mName = name;
+ }
+ }
+
+ private static class MeshSpecificationHolder {
+ public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
+ NativeAllocationRegistry.createMalloced(
+ MeshSpecification.class.getClassLoader(), nativeGetFinalizer());
+ }
+
+ /**
+ * Creates a {@link MeshSpecification} object.
+ *
+ * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
+ * 8.
+ * @param vertexStride length of vertex stride. Max of 1024 is accepted.
+ * @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
+ * @param vertexShader vertex shader to be supplied to the mesh.
+ * @param fragmentShader fragment shader to be suppied 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);
+ if (nativeMeshSpec == 0) {
+ throw new IllegalArgumentException("MeshSpecification construction failed");
+ }
+ return new MeshSpecification(nativeMeshSpec);
+ }
+
+ /**
+ * Creates a {@link MeshSpecification} object.
+ *
+ * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
+ * 8.
+ * @param vertexStride length of vertex stride. Max of 1024 is accepted.
+ * @param varyings List of varyings represented by {@link Varying}. Can hold a max of
+ * 6.
+ * @param vertexShader vertex shader to be supplied to the mesh.
+ * @param fragmentShader fragment shader to be supplied to the mesh.
+ * @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,
+ fragmentShader, colorSpace.getNativeInstance());
+ if (nativeMeshSpec == 0) {
+ throw new IllegalArgumentException("MeshSpecification construction failed");
+ }
+ return new MeshSpecification(nativeMeshSpec);
+ }
+
+ /**
+ * Creates a {@link MeshSpecification} object.
+ *
+ * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
+ * 8.
+ * @param vertexStride length of vertex stride. Max of 1024 is accepted.
+ * @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
+ * @param vertexShader vertex shader code to be supplied to the mesh.
+ * @param fragmentShader fragment shader code to be suppied 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.
+ * @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,
+ @AlphaType int alphaType) {
+ long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
+ fragmentShader, colorSpace.getNativeInstance(), alphaType);
+ if (nativeMeshSpec == 0) {
+ throw new IllegalArgumentException("MeshSpecification construction failed");
+ }
+ return new MeshSpecification(nativeMeshSpec);
+ }
+
+ private MeshSpecification(long meshSpec) {
+ mNativeMeshSpec = meshSpec;
+ MeshSpecificationHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(
+ this, meshSpec);
+ }
+
+ private static native long nativeGetFinalizer();
+
+ private static native long nativeMake(Attribute[] attributes, int vertexStride,
+ Varying[] varyings, String vertexShader, String fragmentShader);
+
+ private static native long nativeMakeWithCS(Attribute[] attributes, int vertexStride,
+ Varying[] varyings, String vertexShader, String fragmentShader, long colorSpace);
+
+ private static native long nativeMakeWithAlpha(Attribute[] attributes, int vertexStride,
+ Varying[] varyings, String vertexShader, String fragmentShader, long colorSpace,
+ int alphaType);
+}
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
new file mode 100644
index 0000000..22fa4d3
--- /dev/null
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+#include <SkMesh.h>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+using Attribute = SkMeshSpecification::Attribute;
+using Varying = SkMeshSpecification::Varying;
+
+static struct {
+ jclass clazz{};
+ jfieldID type{};
+ jfieldID offset{};
+ jfieldID name{};
+} gAttributeInfo;
+
+static struct {
+ jclass clazz{};
+ jfieldID type{};
+ jfieldID name{};
+} gVaryingInfo;
+
+std::vector<Attribute> extractAttributes(JNIEnv* env, jobjectArray attributes) {
+ int size = env->GetArrayLength(attributes);
+ std::vector<Attribute> attVector;
+ attVector.reserve(size);
+ for (int i = 0; i < size; i++) {
+ jobject attribute = env->GetObjectArrayElement(attributes, i);
+ auto name = (jstring)env->GetObjectField(attribute, gAttributeInfo.name);
+ auto attName = ScopedUtfChars(env, name);
+ Attribute temp{Attribute::Type(env->GetIntField(attribute, gAttributeInfo.type)),
+ static_cast<size_t>(env->GetIntField(attribute, gAttributeInfo.offset)),
+ SkString(attName.c_str())};
+ attVector.push_back(std::move(temp));
+ }
+
+ return attVector;
+}
+
+std::vector<Varying> extractVaryings(JNIEnv* env, jobjectArray varyings) {
+ int size = env->GetArrayLength(varyings);
+ std::vector<Varying> varyVector;
+ varyVector.reserve(size);
+ for (int i = 0; i < size; i++) {
+ jobject varying = env->GetObjectArrayElement(varyings, i);
+ auto name = (jstring)env->GetObjectField(varying, gVaryingInfo.name);
+ auto varyName = ScopedUtfChars(env, name);
+ Varying temp{Varying::Type(env->GetIntField(varying, gVaryingInfo.type)),
+ SkString(varyName.c_str())};
+ varyVector.push_back(std::move(temp));
+ }
+
+ return varyVector;
+}
+
+static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+ jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader) {
+ auto attributes = extractAttributes(env, attributeArray);
+ auto varyings = extractVaryings(env, varyingArray);
+ auto skVertexShader = ScopedUtfChars(env, vertexShader);
+ auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+ auto meshSpec = SkMeshSpecification::Make(attributes, vertexStride, varyings,
+ SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()))
+ .specification;
+ return reinterpret_cast<jlong>(meshSpec.release());
+}
+
+static jlong MakeWithCS(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint vertexStride,
+ jobjectArray varyingArray, jstring vertexShader, jstring fragmentShader,
+ jlong colorSpace) {
+ auto attributes = extractAttributes(env, attributeArray);
+ auto varyings = extractVaryings(env, varyingArray);
+ auto skVertexShader = ScopedUtfChars(env, vertexShader);
+ auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+ auto meshSpec = SkMeshSpecification::Make(attributes, vertexStride, varyings,
+ SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()),
+ GraphicsJNI::getNativeColorSpace(colorSpace))
+ .specification;
+
+ return reinterpret_cast<jlong>(meshSpec.release());
+}
+
+static jlong MakeWithAlpha(JNIEnv* env, jobject thiz, jobjectArray attributeArray,
+ jint vertexStride, jobjectArray varyingArray, jstring vertexShader,
+ jstring fragmentShader, jlong colorSpace, jint alphaType) {
+ auto attributes = extractAttributes(env, attributeArray);
+ auto varyings = extractVaryings(env, varyingArray);
+ auto skVertexShader = ScopedUtfChars(env, vertexShader);
+ auto skFragmentShader = ScopedUtfChars(env, fragmentShader);
+ auto meshSpec = SkMeshSpecification::Make(
+ attributes, vertexStride, varyings, SkString(skVertexShader.c_str()),
+ SkString(skFragmentShader.c_str()),
+ GraphicsJNI::getNativeColorSpace(colorSpace), SkAlphaType(alphaType))
+ .specification;
+ return reinterpret_cast<jlong>(meshSpec.release());
+}
+
+static void MeshSpecification_safeUnref(SkMeshSpecification* meshSpec) {
+ SkSafeUnref(meshSpec);
+}
+
+static jlong getMeshSpecificationFinalizer() {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref));
+}
+
+static const JNINativeMethod gMeshSpecificationMethods[] = {
+ {"nativeGetFinalizer", "()J", (void*)getMeshSpecificationFinalizer},
+ {"nativeMake",
+ "([Landroid/graphics/MeshSpecification$Attribute;I[Landroid/graphics/"
+ "MeshSpecification$Varying;"
+ "Ljava/lang/String;Ljava/lang/String;)J",
+ (void*)Make},
+ {"nativeMakeWithCS",
+ "([Landroid/graphics/MeshSpecification$Attribute;I"
+ "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;J)J",
+ (void*)MakeWithCS},
+ {"nativeMakeWithAlpha",
+ "([Landroid/graphics/MeshSpecification$Attribute;I"
+ "[Landroid/graphics/MeshSpecification$Varying;Ljava/lang/String;Ljava/lang/String;JI)J",
+ (void*)MakeWithAlpha}};
+
+int register_android_graphics_MeshSpecification(JNIEnv* env) {
+ android::RegisterMethodsOrDie(env, "android/graphics/MeshSpecification",
+ gMeshSpecificationMethods, NELEM(gMeshSpecificationMethods));
+
+ gAttributeInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Attribute");
+ gAttributeInfo.type = env->GetFieldID(gAttributeInfo.clazz, "mType", "I");
+ gAttributeInfo.offset = env->GetFieldID(gAttributeInfo.clazz, "mOffset", "I");
+ gAttributeInfo.name = env->GetFieldID(gAttributeInfo.clazz, "mName", "Ljava/lang/String;");
+
+ gVaryingInfo.clazz = env->FindClass("android/graphics/MeshSpecification$Varying");
+ gVaryingInfo.type = env->GetFieldID(gVaryingInfo.clazz, "mType", "I");
+ gVaryingInfo.name = env->GetFieldID(gVaryingInfo.clazz, "mName", "Ljava/lang/String;");
+ return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index fab63aa..7039a3e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -998,6 +998,7 @@
private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
private native int nativeStopScan();
private native int nativeSetLnb(Lnb lnb);
+ private native boolean nativeIsLnaSupported();
private native int nativeSetLna(boolean enable);
private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
private native Integer nativeGetAvSyncHwId(Filter filter);
@@ -1382,11 +1383,32 @@
}
/**
+ * Is Low Noise Amplifier (LNA) supported by the Tuner.
+ *
+ * <p>This API is only supported by Tuner HAL 3.0 or higher.
+ * Unsupported version would throw UnsupportedOperationException. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ *
+ * @return {@code true} if supported, otherwise {@code false}.
+ * @throws UnsupportedOperationException if the Tuner HAL version is lower than 3.0
+ * @see android.media.tv.tuner.TunerVersionChecker#TUNER_VERSION_3_0
+ */
+ public boolean isLnaSupported() {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "isLnaSupported")) {
+ throw new UnsupportedOperationException("Tuner HAL version "
+ + TunerVersionChecker.getTunerVersion() + " doesn't support this method.");
+ }
+ return nativeIsLnaSupported();
+ }
+
+ /**
* Enable or Disable Low Noise Amplifier (LNA).
*
* @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
*
- * @return result status of the operation.
+ * @return result status of the operation. {@link #RESULT_UNAVAILABLE} if the device doesn't
+ * support LNA.
*/
@Result
public int setLnaEnabled(boolean enable) {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index a028c04..2afa4d1 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1819,6 +1819,13 @@
return (int)result;
}
+bool JTuner::isLnaSupported() {
+ if (sTunerClient == nullptr) {
+ return (int)Result::NOT_INITIALIZED;
+ }
+ return sTunerClient->isLnaSupported();
+}
+
int JTuner::setLna(bool enable) {
if (sTunerClient == nullptr) {
return (int)Result::NOT_INITIALIZED;
@@ -3562,6 +3569,11 @@
return tuner->setLnb(lnbClient);
}
+static bool android_media_tv_Tuner_is_lna_supported(JNIEnv *env, jobject thiz) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->isLnaSupported();
+}
+
static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean enable) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->setLna(enable);
@@ -4810,6 +4822,7 @@
(void *)android_media_tv_Tuner_scan },
{ "nativeStopScan", "()I", (void *)android_media_tv_Tuner_stop_scan },
{ "nativeSetLnb", "(Landroid/media/tv/tuner/Lnb;)I", (void *)android_media_tv_Tuner_set_lnb },
+ { "nativeIsLnaSupported", "()Z", (void *)android_media_tv_Tuner_is_lna_supported },
{ "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
{ "nativeGetFrontendStatus", "([I)Landroid/media/tv/tuner/frontend/FrontendStatus;",
(void *)android_media_tv_Tuner_get_frontend_status },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index c74b2df..2b69e89 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -189,6 +189,7 @@
int scan(const FrontendSettings& settings, FrontendScanType scanType);
int stopScan();
int setLnb(sp<LnbClient> lnbClient);
+ bool isLnaSupported();
int setLna(bool enable);
jobject openLnbByHandle(int handle);
jobject openLnbByName(jstring name);
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 8515874..ab28fb4 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -210,4 +210,17 @@
return -1;
}
+bool TunerClient::isLnaSupported() {
+ if (mTunerService != nullptr) {
+ bool lnaSupported;
+ Status s = mTunerService->isLnaSupported(&lnaSupported);
+ if (!s.isOk()) {
+ return false;
+ }
+ return lnaSupported;
+ }
+
+ return false;
+}
+
} // namespace android
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 5410c1b..3f8b21c 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -148,6 +148,11 @@
*/
int getMaxNumberOfFrontends(FrontendType frontendType);
+ /**
+ * Is Low Noise Amplifier (LNA) supported.
+ */
+ bool isLnaSupported();
+
private:
/**
* An AIDL Tuner Service assigned at the first time the Tuner Client connects with
diff --git a/packages/CredentialManager/res/drawable/ic_other_sign_in.xml b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
new file mode 100644
index 0000000..8150197
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml
@@ -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.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:name="path"
+ android:pathData="M 20 19 L 12 19 L 12 21 L 20 21 C 21.1 21 22 20.1 22 19 L 22 5 C 22 3.9 21.1 3 20 3 L 12 3 L 12 5 L 20 5 L 20 19 Z"
+ android:fillColor="#000"
+ android:strokeWidth="1"/>
+ <path
+ android:name="path_1"
+ android:pathData="M 12 7 L 10.6 8.4 L 13.2 11 L 8.85 11 C 8.42 9.55 7.09 8.5 5.5 8.5 C 3.57 8.5 2 10.07 2 12 C 2 13.93 3.57 15.5 5.5 15.5 C 7.09 15.5 8.42 14.45 8.85 13 L 13.2 13 L 10.6 15.6 L 12 17 L 17 12 L 12 7 Z M 5.5 13.5 C 4.67 13.5 4 12.83 4 12 C 4 11.17 4.67 10.5 5.5 10.5 C 6.33 10.5 7 11.17 7 12 C 7 12.83 6.33 13.5 5.5 13.5 Z"
+ android:fillColor="#000"
+ android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_password.xml b/packages/CredentialManager/res/drawable/ic_password.xml
new file mode 100644
index 0000000..bf3056a
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_password.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:name="path"
+ android:pathData="M 8.71 10.29 C 8.52 10.1 8.28 10 8 10 L 7.75 10 L 7.75 8.75 C 7.75 7.98 7.48 7.33 6.95 6.8 C 6.42 6.27 5.77 6 5 6 C 4.23 6 3.58 6.27 3.05 6.8 C 2.52 7.33 2.25 7.98 2.25 8.75 L 2.25 10 L 2 10 C 1.72 10 1.48 10.1 1.29 10.29 C 1.1 10.48 1 10.72 1 11 L 1 16 C 1 16.28 1.1 16.52 1.29 16.71 C 1.48 16.9 1.72 17 2 17 L 8 17 C 8.28 17 8.52 16.9 8.71 16.71 C 8.9 16.52 9 16.28 9 16 L 9 11 C 9 10.72 8.9 10.48 8.71 10.29 Z M 6.25 10 L 3.75 10 L 3.75 8.75 C 3.75 8.4 3.87 8.1 4.11 7.86 C 4.35 7.62 4.65 7.5 5 7.5 C 5.35 7.5 5.65 7.62 5.89 7.86 C 6.13 8.1 6.25 8.4 6.25 8.75 L 6.25 10 Z M 10 14 L 23 14 L 23 16 L 10 16 Z M 21.5 9 C 21.102 9 20.721 9.158 20.439 9.439 C 20.158 9.721 20 10.102 20 10.5 C 20 10.898 20.158 11.279 20.439 11.561 C 20.721 11.842 21.102 12 21.5 12 C 21.898 12 22.279 11.842 22.561 11.561 C 22.842 11.279 23 10.898 23 10.5 C 23 10.102 22.842 9.721 22.561 9.439 C 22.279 9.158 21.898 9 21.5 9 Z M 16.5 9 C 16.102 9 15.721 9.158 15.439 9.439 C 15.158 9.721 15 10.102 15 10.5 C 15 10.898 15.158 11.279 15.439 11.561 C 15.721 11.842 16.102 12 16.5 12 C 16.898 12 17.279 11.842 17.561 11.561 C 17.842 11.279 18 10.898 18 10.5 C 18 10.102 17.842 9.721 17.561 9.439 C 17.279 9.158 16.898 9 16.5 9 Z M 11.5 9 C 11.102 9 10.721 9.158 10.439 9.439 C 10.158 9.721 10 10.102 10 10.5 C 10 10.898 10.158 11.279 10.439 11.561 C 10.721 11.842 11.102 12 11.5 12 C 11.898 12 12.279 11.842 12.561 11.561 C 12.842 11.279 13 10.898 13 10.5 C 13 10.102 12.842 9.721 12.561 9.439 C 12.279 9.158 11.898 9 11.5 9 Z"
+ android:fillColor="#000"
+ android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 1ee2a26..0882af7 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -34,6 +34,7 @@
<string name="passkey">passkey</string>
<string name="password">password</string>
<string name="sign_ins">sign-ins</string>
+ <string name="passkey_before_subtitle">Passkey</string>
<string name="another_device">Another device</string>
<string name="other_password_manager">Other password managers</string>
<!-- TODO: Check the wording here. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 2bede9a..244a11fd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -39,16 +39,15 @@
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
+import android.service.credentials.CredentialProviderService
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
-import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
-import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest.Companion.createFrom
-import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
// Consider repo per screen, similar to view model?
@@ -66,7 +65,7 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testCreateRequestInfo()
+ ) ?: testCreatePasskeyRequestInfo()
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
@@ -136,9 +135,10 @@
}
fun createCredentialInitialUiState(): CreateCredentialUiState {
+ val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
// Handle runtime cast error
- providerEnabledList as List<CreateCredentialProviderData>, context)
+ providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
providerDisabledList as List<DisabledProviderData>, context)
@@ -147,21 +147,6 @@
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
- // TODO: covert from real requestInfo for create passkey
- var requestDisplayInfo = RequestDisplayInfo(
- "beckett-bakert@gmail.com",
- "Elisa Beckett",
- TYPE_PUBLIC_KEY_CREDENTIAL,
- "tribank")
- val createCredentialRequest = requestInfo.createCredentialRequest
- val createCredentialRequestJetpack = createCredentialRequest?.let { createFrom(it) }
- if (createCredentialRequestJetpack is CreatePasswordRequest) {
- requestDisplayInfo = RequestDisplayInfo(
- createCredentialRequestJetpack.id,
- createCredentialRequestJetpack.password,
- TYPE_PASSWORD_CREDENTIAL,
- "tribank")
- }
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
@@ -390,11 +375,12 @@
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
or PendingIntent.FLAG_ONE_SHOT))
val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
- context.applicationInfo.packageName,
- "PASSWORD",
- toBundle("beckett-bakert@gmail.com", "password123")
+ context.applicationInfo.packageName,
+ TYPE_PASSWORD_CREDENTIAL,
+ toBundle("beckett-bakert@gmail.com", "password123")
)
- val fillInIntent = Intent().putExtra("create_request_params", createPasswordRequest)
+ val fillInIntent = Intent().putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
+ createPasswordRequest)
val slice = Slice.Builder(
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
@@ -438,8 +424,9 @@
)
}
- private fun testCreateRequestInfo(): RequestInfo {
- val data = toBundle("beckett-bakert@gmail.com", "password123")
+ private fun testCreatePasskeyRequestInfo(): RequestInfo {
+ val request = CreatePublicKeyCredentialRequest("json")
+ val data = request.data
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
@@ -451,6 +438,32 @@
)
}
+ private fun testCreatePasswordRequestInfo(): RequestInfo {
+ val data = toBundle("beckett-bakert@gmail.com", "password123")
+ return RequestInfo.newCreateRequestInfo(
+ Binder(),
+ CreateCredentialRequest(
+ TYPE_PASSWORD_CREDENTIAL,
+ data
+ ),
+ /*isFirstUsage=*/false,
+ "tribank"
+ )
+ }
+
+ private fun testCreateOtherCredentialRequestInfo(): RequestInfo {
+ val data = Bundle()
+ return RequestInfo.newCreateRequestInfo(
+ Binder(),
+ CreateCredentialRequest(
+ "other-sign-ins",
+ data
+ ),
+ /*isFirstUsage=*/false,
+ "tribank"
+ )
+ }
+
private fun testGetRequestInfo(): RequestInfo {
return RequestInfo.newGetRequestInfo(
Binder(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 2eb3284..c33af8f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -23,14 +23,19 @@
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.DisabledProviderData
+import android.credentials.ui.RequestInfo
import android.graphics.drawable.Drawable
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
import com.android.credentialmanager.getflow.CredentialEntryInfo
import com.android.credentialmanager.getflow.ProviderInfo
import com.android.credentialmanager.getflow.RemoteEntryInfo
+import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
+import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.provider.ActionUi
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
@@ -172,6 +177,7 @@
fun toEnabledProviderList(
providerDataList: List<CreateCredentialProviderData>,
+ requestDisplayInfo: RequestDisplayInfo,
context: Context,
): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
// TODO: get from the actual service info
@@ -194,7 +200,7 @@
name = it.providerFlattenedComponentName,
displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
createOptions = toCreationOptionInfoList(
- it.providerFlattenedComponentName, it.saveEntries, context),
+ it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
isDefault = it.isDefaultProvider,
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
)
@@ -219,9 +225,50 @@
}
}
+ fun toRequestDisplayInfo(
+ requestInfo: RequestInfo,
+ context: Context,
+ ): RequestDisplayInfo {
+ val createCredentialRequest = requestInfo.createCredentialRequest
+ val createCredentialRequestJetpack = createCredentialRequest?.let {
+ CreateCredentialRequest.createFrom(
+ it
+ )
+ }
+ // TODO: covert from real requestInfo
+ when (createCredentialRequestJetpack) {
+ is CreatePasswordRequest -> {
+ return RequestDisplayInfo(
+ createCredentialRequestJetpack.id,
+ createCredentialRequestJetpack.password,
+ createCredentialRequestJetpack.type,
+ "tribank",
+ context.getDrawable(R.drawable.ic_password)!!
+ )
+ }
+ is CreatePublicKeyCredentialRequest -> {
+ return RequestDisplayInfo(
+ "beckett-bakert@gmail.com",
+ "Elisa Beckett",
+ createCredentialRequestJetpack.type,
+ "tribank",
+ context.getDrawable(R.drawable.ic_passkey)!!)
+ }
+ else -> {
+ return RequestDisplayInfo(
+ "beckett-bakert@gmail.com",
+ "Elisa Beckett",
+ "other-sign-ins",
+ "tribank",
+ context.getDrawable(R.drawable.ic_other_sign_in)!!)
+ }
+ }
+ }
+
private fun toCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,
+ requestDisplayInfo: RequestDisplayInfo,
context: Context,
): List<CreateOptionInfo> {
return creationEntries.map {
@@ -236,7 +283,7 @@
fillInIntent = it.frameworkExtrasIntent,
userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
- ?: context.getDrawable(R.drawable.ic_profile)!!,
+ ?: requestDisplayInfo.typeIcon,
passwordCount = saveEntryUi.passwordCount ?: 0,
passkeyCount = saveEntryUi.passkeyCount ?: 0,
totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 9f73aef..c9cb3ce 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -191,13 +191,12 @@
) {
Card() {
Column() {
- // TODO: Change the icon for create passwords and sign-ins
Icon(
- painter = painterResource(R.drawable.ic_passkey),
+ bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(top = 24.dp, bottom = 16.dp)
+ .padding(top = 24.dp, bottom = 16.dp).size(32.dp)
)
Text(
text = stringResource(
@@ -567,35 +566,52 @@
Entry(
onClick = {onOptionSelected(createOptionInfo)},
icon = {
- // TODO: Upload the other two types icons and change it according to request types
Icon(
- painter = painterResource(R.drawable.ic_passkey),
+ bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- modifier = Modifier.padding(start = 18.dp)
+ modifier = Modifier.padding(start = 18.dp).size(32.dp)
)
},
label = {
Column() {
// TODO: Add the function to hide/view password when the type is create password
- if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
- requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
- Text(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = requestDisplayInfo.subtitle,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- } else {
- Text(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
- )
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL -> {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = if (requestDisplayInfo.subtitle != null) {
+ stringResource(
+ R.string.passkey_before_subtitle) + " - " + requestDisplayInfo.subtitle
+ } else {stringResource(R.string.passkey_before_subtitle)},
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ TYPE_PASSWORD_CREDENTIAL -> {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ // This subtitle would never be null for create password
+ text = requestDisplayInfo.subtitle ?: "",
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ else -> {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
+ )
+ }
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 6dd6afb..31d0365 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -73,9 +73,10 @@
data class RequestDisplayInfo(
val title: String,
- val subtitle: String,
+ val subtitle: String?,
val type: String,
val appDomainName: String,
+ val typeIcon: Drawable,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
index 26d61f9..37a4f76 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
@@ -47,7 +47,7 @@
return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
CreatePublicKeyCredentialRequest
.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
- CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
+ CreatePublicKeyCredentialRequest.createFrom(data)
CreatePublicKeyCredentialRequestPrivileged
.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED ->
CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index e4bdab8..88c1036 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -28,7 +28,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
@@ -104,10 +103,9 @@
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageNameFromIntent != null) {
final String callingPkgName = getLaunchedFromPackage();
- if (installerPackageNameFromIntent.length() >= SessionParams.MAX_PACKAGE_NAME_LENGTH
- || (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
+ if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
&& mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
- callingPkgName) != PackageManager.PERMISSION_GRANTED)) {
+ callingPkgName) != PackageManager.PERMISSION_GRANTED) {
Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent
+ " is invalid. Remove it.");
EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
index 96e2498..decc292 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
@@ -46,7 +46,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
index a57df0f..063b61c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
@@ -50,7 +50,6 @@
)
fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 7958d11..42ac1ac 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -55,7 +55,6 @@
entryList.add(
createEntry(owner, EntryEnum.STRING_PARAM)
// Set attributes
- .setIsAllowSearch(true)
.setIsSearchDataDynamic(true)
.setSearchDataFn { ArgumentPageModel.genStringParamSearchData() }
.setUiLayoutFn {
@@ -67,7 +66,6 @@
entryList.add(
createEntry(owner, EntryEnum.INT_PARAM)
// Set attributes
- .setIsAllowSearch(true)
.setIsSearchDataDynamic(true)
.setSearchDataFn { ArgumentPageModel.genIntParamSearchData() }
.setUiLayoutFn {
@@ -90,8 +88,6 @@
owner = createSettingsPage(arguments),
displayName = "${name}_$stringParam",
)
- // Set attributes
- .setIsAllowSearch(false)
.setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
.setUiLayoutFn {
// Set ui rendering
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
index 160e77b..7f21a4d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -134,7 +134,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index c903cfd..9f24ea9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -20,6 +20,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -42,7 +43,7 @@
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "Some Preference", owner)
- .setIsAllowSearch(true)
+ .setSearchDataFn { EntrySearchData(title = "Some Preference") }
.setUiLayoutFn {
Preference(remember {
object : PreferenceModel {
@@ -58,7 +59,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index e10cf3a..ddf66aa 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -72,7 +72,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
index c354930..4332a81 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
@@ -46,7 +46,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
index 1f76557..20d90dd 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
@@ -48,7 +48,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index cb58a95..c0d0abc 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -40,7 +40,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 73b34a5..a62ec7b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -48,7 +48,6 @@
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create("Simple Slider", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SliderPreference(object : SliderPreferenceModel {
override val title = "Simple Slider"
@@ -58,7 +57,6 @@
)
entryList.add(
SettingsEntryBuilder.create("Slider with icon", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SliderPreference(object : SliderPreferenceModel {
override val title = "Slider with icon"
@@ -72,7 +70,6 @@
)
entryList.add(
SettingsEntryBuilder.create("Slider with changeable icon", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
val initValue = 0
var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
@@ -93,7 +90,6 @@
)
entryList.add(
SettingsEntryBuilder.create("Slider with steps", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SliderPreference(object : SliderPreferenceModel {
override val title = "Slider with steps"
@@ -109,7 +105,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index f38a8d4..67e35dc 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -44,14 +44,12 @@
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "MainSwitchPreference", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleMainSwitchPreference()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "MainSwitchPreference not changeable", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleNotChangeableMainSwitchPreference()
}.build()
@@ -62,7 +60,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
index 61925a7..eddede7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
@@ -43,7 +43,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index ff89f2b..0c9a043 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -87,7 +87,6 @@
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
createEntry(EntryEnum.SIMPLE_PREFERENCE)
- .setIsAllowSearch(true)
.setMacro {
spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
@@ -96,7 +95,6 @@
)
entryList.add(
createEntry(EntryEnum.SUMMARY_PREFERENCE)
- .setIsAllowSearch(true)
.setMacro {
spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
SimplePreferenceMacro(
@@ -110,7 +108,6 @@
entryList.add(singleLineSummaryEntry())
entryList.add(
createEntry(EntryEnum.DISABLED_PREFERENCE)
- .setIsAllowSearch(true)
.setHasMutableStatus(true)
.setMacro {
spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
@@ -126,7 +123,6 @@
)
entryList.add(
createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
- .setIsAllowSearch(true)
.setHasMutableStatus(true)
.setSearchDataFn {
EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
@@ -165,7 +161,6 @@
)
entryList.add(
createEntry(EntryEnum.MANUAL_UPDATE_PREFERENCE)
- .setIsAllowSearch(true)
.setUiLayoutFn {
val model = PreferencePageModel.create()
val manualUpdaterSummary = remember { model.getManualUpdaterSummary() }
@@ -179,7 +174,8 @@
}
}
)
- }.setSliceDataFn { sliceUri, args ->
+ }
+ .setSliceDataFn { sliceUri, args ->
val createSliceImpl = { v: Int ->
createDemoActionSlice(
sliceUri = sliceUri,
@@ -204,7 +200,6 @@
)
entryList.add(
createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
- .setIsAllowSearch(true)
.setUiLayoutFn {
val model = PreferencePageModel.create()
val autoUpdaterSummary = remember { model.getAutoUpdaterSummary() }
@@ -251,7 +246,6 @@
}
private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(
model = object : PreferenceModel {
@@ -267,7 +261,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = owner)
- .setIsAllowSearch(true)
.setMacro {
spaLogger.message(TAG, "create macro for INJECT entry")
SimplePreferenceMacro(
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index dab04fd..067911c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -49,35 +49,30 @@
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "SwitchPreference", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleSwitchPreference()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "SwitchPreference with summary", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleSwitchPreferenceWithSummary()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "SwitchPreference with async summary", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleSwitchPreferenceWithAsyncSummary()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "SwitchPreference not changeable", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleNotChangeableSwitchPreference()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "SwitchPreference with icon", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleSwitchPreferenceWithIcon()
}.build()
@@ -88,7 +83,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index 22da99c..33e5e8d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -46,28 +46,24 @@
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleTwoTargetSwitchPreference()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "TwoTargetSwitchPreference with summary", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleTwoTargetSwitchPreferenceWithSummary()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "TwoTargetSwitchPreference with async summary", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleTwoTargetSwitchPreferenceWithAsyncSummary()
}.build()
)
entryList.add(
SettingsEntryBuilder.create( "TwoTargetSwitchPreference not changeable", owner)
- .setIsAllowSearch(true)
.setUiLayoutFn {
SampleNotChangeableTwoTargetSwitchPreference()
}.build()
@@ -78,7 +74,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index d87cbe8..cb58abf6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -39,7 +39,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
index ec2f436..ba769d2 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -40,7 +40,6 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
- .setIsAllowSearch(true)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index c3c90ab..6ed7481 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -19,6 +19,7 @@
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -37,6 +38,7 @@
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LocalNavController
@@ -74,80 +76,13 @@
setContent {
SettingsTheme {
- MainContent()
- }
- }
- }
-
- @Composable
- private fun MainContent() {
- val sppRepository by spaEnvironment.pageProviderRepository
- val navController = rememberNavController()
- val nullPage = SettingsPage.createNull()
- CompositionLocalProvider(navController.localNavController()) {
- NavHost(
- navController = navController,
- startDestination = nullPage.sppName,
- ) {
- composable(nullPage.sppName) {}
- for (spp in sppRepository.getAllProviders()) {
- composable(
- route = spp.name + spp.parameter.navRoute(),
- arguments = spp.parameter,
- ) { navBackStackEntry ->
- PageLogger(remember(navBackStackEntry.arguments) {
- spp.createSettingsPage(arguments = navBackStackEntry.arguments)
- })
-
- spp.Page(navBackStackEntry.arguments)
- }
- }
- }
- InitialDestinationNavigator()
- }
- }
-
- @Composable
- private fun PageLogger(settingsPage: SettingsPage) {
- val lifecycleOwner = LocalLifecycleOwner.current
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- settingsPage.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- settingsPage.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
- }
- }
-
- @Composable
- private fun InitialDestinationNavigator() {
- val sppRepository by spaEnvironment.pageProviderRepository
- val destinationNavigated = rememberSaveable { mutableStateOf(false) }
- if (destinationNavigated.value) return
- destinationNavigated.value = true
- val controller = LocalNavController.current as NavControllerWrapperImpl
- LaunchedEffect(Unit) {
- val destination =
- intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPage()
- val highlightEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
- if (destination.isNotEmpty()) {
- controller.highlightId = highlightEntryId
- val navController = controller.navController
- navController.navigate(destination) {
- popUpTo(navController.graph.findStartDestination().id) {
- inclusive = true
- }
- }
+ val sppRepository by spaEnvironment.pageProviderRepository
+ BrowseContent(
+ allProviders = sppRepository.getAllProviders(),
+ initialDestination = intent?.getStringExtra(KEY_DESTINATION)
+ ?: sppRepository.getDefaultStartPage(),
+ initialEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
+ )
}
}
}
@@ -157,3 +92,81 @@
const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
}
}
+
+@VisibleForTesting
+@Composable
+fun BrowseContent(
+ allProviders: Collection<SettingsPageProvider>,
+ initialDestination: String,
+ initialEntryId: String?
+) {
+ val navController = rememberNavController()
+ CompositionLocalProvider(navController.localNavController()) {
+ val controller = LocalNavController.current as NavControllerWrapperImpl
+ controller.NavContent(allProviders)
+ controller.InitialDestination(initialDestination, initialEntryId)
+ }
+}
+
+@Composable
+private fun SettingsPageProvider.PageEvents(arguments: Bundle? = null) {
+ val page = remember(arguments) { createSettingsPage(arguments) }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ page.enterPage()
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ page.leavePage()
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+}
+
+@Composable
+private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
+ val nullPage = SettingsPage.createNull()
+ NavHost(
+ navController = navController,
+ startDestination = nullPage.sppName,
+ ) {
+ composable(nullPage.sppName) {}
+ for (spp in allProvider) {
+ composable(
+ route = spp.name + spp.parameter.navRoute(),
+ arguments = spp.parameter,
+ ) { navBackStackEntry ->
+ spp.PageEvents(navBackStackEntry.arguments)
+ spp.Page(navBackStackEntry.arguments)
+ }
+ }
+ }
+}
+
+@Composable
+private fun NavControllerWrapperImpl.InitialDestination(
+ destination: String,
+ highlightEntryId: String?
+) {
+ val destinationNavigated = rememberSaveable { mutableStateOf(false) }
+ if (destinationNavigated.value) return
+ destinationNavigated.value = true
+
+ if (destination.isEmpty()) return
+ LaunchedEffect(Unit) {
+ highlightId = highlightEntryId
+ navController.navigate(destination) {
+ popUpTo(navController.graph.findStartDestination().id) {
+ inclusive = true
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index a372bbd..82e05a5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -156,6 +156,15 @@
}
}
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+ return SettingsPage.create(
+ name = name,
+ displayName = displayName,
+ parameter = parameter,
+ arguments = arguments
+ )
+}
+
fun String.toHashId(): String {
return this.hashCode().toUInt().toString(36)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 60599d4..940005d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -51,12 +51,3 @@
}
}
}
-
-fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
- return SettingsPage.create(
- name = name,
- displayName = displayName,
- parameter = parameter,
- arguments = arguments
- )
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
new file mode 100644
index 0000000..bde3bba
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework
+
+import android.content.Context
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.testutils.waitUntil
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val WAIT_UNTIL_TIMEOUT = 1000L
+
+@RunWith(AndroidJUnit4::class)
+class BrowseActivityTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaLogger = SpaLoggerForTest()
+ private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+
+ @Test
+ fun testBrowsePage() {
+ spaLogger.reset()
+ SpaEnvironmentFactory.reset(spaEnvironment)
+
+ val sppRepository by spaEnvironment.pageProviderRepository
+ val sppHome = sppRepository.getProviderOrNull("SppHome")!!
+ val pageHome = sppHome.createSettingsPage()
+ val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
+ val pageLayer1 = sppLayer1.createSettingsPage()
+
+ composeTestRule.setContent {
+ BrowseContent(
+ allProviders = listOf(sppHome, sppLayer1),
+ initialDestination = pageHome.buildRoute(),
+ initialEntryId = null
+ )
+ }
+
+ composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
+ spaLogger.verifyPageEvent(pageHome.id, 1, 0)
+ spaLogger.verifyPageEvent(pageLayer1.id, 0, 0)
+
+ // click to layer1 page
+ composeTestRule.onNodeWithText("SppHome to Layer1").assertIsDisplayed().performClick()
+ waitUntil(WAIT_UNTIL_TIMEOUT) {
+ composeTestRule.onAllNodesWithText(sppLayer1.getTitle(null))
+ .fetchSemanticsNodes().size == 1
+ }
+ spaLogger.verifyPageEvent(pageHome.id, 1, 1)
+ spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
+ }
+}
+
+private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
+ Truth.assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+ .isEqualTo(entryCount)
+ Truth.assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+ .isEqualTo(leaveCount)
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index a404dd3..ab269f2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -35,6 +35,7 @@
import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.SpaLogger
import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
class SpaLoggerForTest : SpaLogger {
data class MsgCountKey(val msg: String, val category: LogCategory)
@@ -98,6 +99,12 @@
fun buildInject(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(this.createSettingsPage())
+ .setMacro {
+ SimplePreferenceMacro(
+ title = "SppHome to Layer1",
+ clickRoute = name
+ )
+ }
}
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index bb1cd6e..76f6611 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spaprivileged.framework.common
+import android.app.ActivityManager
import android.app.AlarmManager
import android.app.AppOpsManager
import android.app.admin.DevicePolicyManager
@@ -28,6 +29,9 @@
import android.os.UserManager
import android.permission.PermissionControllerManager
+/** The [ActivityManager] instance. */
+val Context.activityManager get() = getSystemService(ActivityManager::class.java)!!
+
/** The [AlarmManager] instance. */
val Context.alarmManager get() = getSystemService(AlarmManager::class.java)!!
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2e0155b..caaa88d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1105,6 +1105,8 @@
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
<string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
+ <!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
+ <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 65c94ce..ca5f57d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1598,6 +1598,11 @@
*/
public boolean isHomeApp;
+ /**
+ * Whether or not it's a cloned app .
+ */
+ public boolean isCloned;
+
public String getNormalizedLabel() {
if (normalizedLabel != null) {
return normalizedLabel;
@@ -1637,7 +1642,12 @@
ThreadUtils.postOnBackgroundThread(
() -> this.ensureLabelDescriptionLocked(context));
}
- this.showInPersonalTab = shouldShowInPersonalTab(context, info.uid);
+ UserManager um = UserManager.get(context);
+ this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
+ UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid));
+ if (userInfo != null) {
+ this.isCloned = userInfo.isCloneProfile();
+ }
}
/**
@@ -1645,8 +1655,7 @@
* {@link UserProperties#SHOW_IN_SETTINGS_WITH_PARENT} set.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- boolean shouldShowInPersonalTab(Context context, int uid) {
- UserManager userManager = UserManager.get(context);
+ boolean shouldShowInPersonalTab(UserManager userManager, int uid) {
int userId = UserHandle.getUserId(uid);
// Regardless of apk version, if the app belongs to the current user then return true.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 010d738..b929f8c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -77,9 +77,7 @@
private final LocalBluetoothProfileManager mProfileManager;
private final Object mProfileLock = new Object();
BluetoothDevice mDevice;
- private int mDeviceSide;
- private int mDeviceMode;
- private long mHiSyncId;
+ private HearingAidInfo mHearingAidInfo;
private int mGroupId;
// Need this since there is no method for getting RSSI
@@ -160,9 +158,6 @@
mProfileManager = profileManager;
mDevice = device;
fillData();
- mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
- mDeviceSide = HearingAidProfile.DeviceSide.SIDE_INVALID;
- mDeviceMode = HearingAidProfile.DeviceMode.MODE_INVALID;
mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
initDrawableCache();
mUnpairing = false;
@@ -341,28 +336,34 @@
connectDevice();
}
- public int getDeviceSide() {
- return mDeviceSide;
+ public HearingAidInfo getHearingAidInfo() {
+ return mHearingAidInfo;
}
- public void setDeviceSide(int side) {
- mDeviceSide = side;
+ public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+ mHearingAidInfo = hearingAidInfo;
+ }
+
+ /**
+ * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device
+ */
+ public boolean isHearingAidDevice() {
+ return mHearingAidInfo != null;
+ }
+
+ public int getDeviceSide() {
+ return mHearingAidInfo != null
+ ? mHearingAidInfo.getSide() : HearingAidInfo.DeviceSide.SIDE_INVALID;
}
public int getDeviceMode() {
- return mDeviceMode;
- }
-
- public void setDeviceMode(int mode) {
- mDeviceMode = mode;
+ return mHearingAidInfo != null
+ ? mHearingAidInfo.getMode() : HearingAidInfo.DeviceMode.MODE_INVALID;
}
public long getHiSyncId() {
- return mHiSyncId;
- }
-
- public void setHiSyncId(long id) {
- mHiSyncId = id;
+ return mHearingAidInfo != null
+ ? mHearingAidInfo.getHiSyncId() : BluetoothHearingAid.HI_SYNC_ID_INVALID;
}
/**
@@ -1169,9 +1170,10 @@
if (subDevice != null && subDevice.isConnected()) {
stringRes = R.string.bluetooth_hearing_aid_left_and_right_active;
} else {
- if (mDeviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT) {
+ int deviceSide = getDeviceSide();
+ if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
stringRes = R.string.bluetooth_hearing_aid_left_active;
- } else if (mDeviceSide == HearingAidProfile.DeviceSide.SIDE_RIGHT) {
+ } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
stringRes = R.string.bluetooth_hearing_aid_right_active;
} else {
stringRes = R.string.bluetooth_active_no_battery_level;
@@ -1397,7 +1399,7 @@
* The device may be an ASHA hearing aid that supports {@link HearingAidProfile} or a LeAudio
* hearing aid that supports {@link HapClientProfile} and {@link LeAudioProfile}.
*/
- public boolean isHearingAidDevice() {
+ public boolean isConnectedHearingAidDevice() {
return isConnectedAshaHearingAidDevice() || isConnectedLeAudioHearingAidDevice();
}
@@ -1429,17 +1431,17 @@
BluetoothDevice tmpDevice = mDevice;
final short tmpRssi = mRssi;
final boolean tmpJustDiscovered = mJustDiscovered;
- final int tmpDeviceSide = mDeviceSide;
+ final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
// Set main device from sub device
mDevice = mSubDevice.mDevice;
mRssi = mSubDevice.mRssi;
mJustDiscovered = mSubDevice.mJustDiscovered;
- mDeviceSide = mSubDevice.mDeviceSide;
+ mHearingAidInfo = mSubDevice.mHearingAidInfo;
// Set sub device from backup
mSubDevice.mDevice = tmpDevice;
mSubDevice.mRssi = tmpRssi;
mSubDevice.mJustDiscovered = tmpJustDiscovered;
- mSubDevice.mDeviceSide = tmpDeviceSide;
+ mSubDevice.mHearingAidInfo = tmpHearingAidInfo;
fetchActiveDevices();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index cf4e1ee..ebfec0a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -27,7 +27,7 @@
import java.util.Set;
/**
- * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices.
+ * HearingAidDeviceManager manages the set of remote HearingAid(ASHA) Bluetooth devices.
*/
public class HearingAidDeviceManager {
private static final String TAG = "HearingAidDeviceManager";
@@ -44,12 +44,12 @@
void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
long hiSyncId = getHiSyncId(newDevice.getDevice());
if (isValidHiSyncId(hiSyncId)) {
- // Once hiSyncId is valid, assign hiSyncId
- newDevice.setHiSyncId(hiSyncId);
- final int side = getDeviceSide(newDevice.getDevice());
- final int mode = getDeviceMode(newDevice.getDevice());
- newDevice.setDeviceSide(side);
- newDevice.setDeviceMode(mode);
+ // Once hiSyncId is valid, assign hearing aid info
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setAshaDeviceSide(getDeviceSide(newDevice.getDevice()))
+ .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
+ .setHiSyncId(hiSyncId);
+ newDevice.setHearingAidInfo(infoBuilder.build());
}
}
@@ -123,12 +123,14 @@
final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
// Do nothing if there is no HiSyncId on Bluetooth device
if (isValidHiSyncId(newHiSyncId)) {
- cachedDevice.setHiSyncId(newHiSyncId);
+ // Once hiSyncId is valid, assign hearing aid info
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setAshaDeviceSide(getDeviceSide(cachedDevice.getDevice()))
+ .setAshaDeviceMode(getDeviceMode(cachedDevice.getDevice()))
+ .setHiSyncId(newHiSyncId);
+ cachedDevice.setHearingAidInfo(infoBuilder.build());
+
newSyncIdSet.add(newHiSyncId);
- final int side = getDeviceSide(cachedDevice.getDevice());
- final int mode = getDeviceMode(cachedDevice.getDevice());
- cachedDevice.setDeviceSide(side);
- cachedDevice.setDeviceMode(mode);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
new file mode 100644
index 0000000..ef08c92
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
@@ -0,0 +1,263 @@
+/*
+ * 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.bluetooth;
+
+import android.annotation.IntDef;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
+import android.util.SparseIntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** Hearing aids information and constants that shared within hearing aids related profiles */
+public class HearingAidInfo {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DeviceSide.SIDE_INVALID,
+ DeviceSide.SIDE_LEFT,
+ DeviceSide.SIDE_RIGHT,
+ DeviceSide.SIDE_LEFT_AND_RIGHT,
+ })
+
+ /** Side definition for hearing aids. */
+ public @interface DeviceSide {
+ int SIDE_INVALID = -1;
+ int SIDE_LEFT = 0;
+ int SIDE_RIGHT = 1;
+ int SIDE_LEFT_AND_RIGHT = 2;
+ }
+
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @IntDef({
+ DeviceMode.MODE_INVALID,
+ DeviceMode.MODE_MONAURAL,
+ DeviceMode.MODE_BINAURAL,
+ DeviceMode.MODE_BANDED,
+ })
+
+ /** Mode definition for hearing aids. */
+ public @interface DeviceMode {
+ int MODE_INVALID = -1;
+ int MODE_MONAURAL = 0;
+ int MODE_BINAURAL = 1;
+ int MODE_BANDED = 2;
+ }
+
+ private final int mSide;
+ private final int mMode;
+ private final long mHiSyncId;
+
+ private HearingAidInfo(int side, int mode, long hiSyncId) {
+ mSide = side;
+ mMode = mode;
+ mHiSyncId = hiSyncId;
+ }
+
+ @DeviceSide
+ public int getSide() {
+ return mSide;
+ }
+
+ @DeviceMode
+ public int getMode() {
+ return mMode;
+ }
+
+ public long getHiSyncId() {
+ return mHiSyncId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof HearingAidInfo)) {
+ return false;
+ }
+ HearingAidInfo that = (HearingAidInfo) o;
+ return mSide == that.mSide && mMode == that.mMode && mHiSyncId == that.mHiSyncId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSide, mMode, mHiSyncId);
+ }
+
+ @Override
+ public String toString() {
+ return "HearingAidInfo{"
+ + "mSide=" + mSide
+ + ", mMode=" + mMode
+ + ", mHiSyncId=" + mHiSyncId
+ + '}';
+ }
+
+ @DeviceSide
+ private static int convertAshaDeviceSideToInternalSide(int ashaDeviceSide) {
+ return ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.get(
+ ashaDeviceSide, DeviceSide.SIDE_INVALID);
+ }
+
+ @DeviceMode
+ private static int convertAshaDeviceModeToInternalMode(int ashaDeviceMode) {
+ return ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.get(
+ ashaDeviceMode, DeviceMode.MODE_INVALID);
+ }
+
+ @DeviceSide
+ private static int convertLeAudioLocationToInternalSide(int leAudioLocation) {
+ boolean isLeft = (leAudioLocation & LE_AUDIO_LOCATION_LEFT) != 0;
+ boolean isRight = (leAudioLocation & LE_AUDIO_LOCATION_RIGHT) != 0;
+ if (isLeft && isRight) {
+ return DeviceSide.SIDE_LEFT_AND_RIGHT;
+ } else if (isLeft) {
+ return DeviceSide.SIDE_LEFT;
+ } else if (isRight) {
+ return DeviceSide.SIDE_RIGHT;
+ }
+ return DeviceSide.SIDE_INVALID;
+ }
+
+ @DeviceMode
+ private static int convertHapDeviceTypeToInternalMode(int hapDeviceType) {
+ return HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.get(hapDeviceType, DeviceMode.MODE_INVALID);
+ }
+
+ /** Builder class for constructing {@link HearingAidInfo} objects. */
+ public static final class Builder {
+ private int mSide = DeviceSide.SIDE_INVALID;
+ private int mMode = DeviceMode.MODE_INVALID;
+ private long mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+
+ /**
+ * Configure the hearing device mode.
+ * @param ashaDeviceMode one of the hearing aid device modes defined in HearingAidProfile
+ * {@link HearingAidProfile.DeviceMode}
+ */
+ public Builder setAshaDeviceMode(int ashaDeviceMode) {
+ mMode = convertAshaDeviceModeToInternalMode(ashaDeviceMode);
+ return this;
+ }
+
+ /**
+ * Configure the hearing device mode.
+ * @param hapDeviceType one of the hearing aid device types defined in HapClientProfile
+ * {@link HapClientProfile.HearingAidType}
+ */
+ public Builder setHapDeviceType(int hapDeviceType) {
+ mMode = convertHapDeviceTypeToInternalMode(hapDeviceType);
+ return this;
+ }
+
+ /**
+ * Configure the hearing device side.
+ * @param ashaDeviceSide one of the hearing aid device sides defined in HearingAidProfile
+ * {@link HearingAidProfile.DeviceSide}
+ */
+ public Builder setAshaDeviceSide(int ashaDeviceSide) {
+ mSide = convertAshaDeviceSideToInternalSide(ashaDeviceSide);
+ return this;
+ }
+
+ /**
+ * Configure the hearing device side.
+ * @param leAudioLocation one of the audio location defined in BluetoothLeAudio
+ * {@link BluetoothLeAudio.AudioLocation}
+ */
+ public Builder setLeAudioLocation(int leAudioLocation) {
+ mSide = convertLeAudioLocationToInternalSide(leAudioLocation);
+ return this;
+ }
+
+ /**
+ * Configure the hearing aid hiSyncId.
+ * @param hiSyncId the ASHA hearing aid id
+ */
+ public Builder setHiSyncId(long hiSyncId) {
+ mHiSyncId = hiSyncId;
+ return this;
+ }
+
+ /** Build the configured {@link HearingAidInfo} */
+ public HearingAidInfo build() {
+ return new HearingAidInfo(mSide, mMode, mHiSyncId);
+ }
+ }
+
+ private static final int LE_AUDIO_LOCATION_LEFT =
+ BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_BACK_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_OF_CENTER
+ | BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_WIDE
+ | BluetoothLeAudio.AUDIO_LOCATION_LEFT_SURROUND;
+
+ private static final int LE_AUDIO_LOCATION_RIGHT =
+ BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_BACK_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER
+ | BluetoothLeAudio.AUDIO_LOCATION_SIDE_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_WIDE
+ | BluetoothLeAudio.AUDIO_LOCATION_RIGHT_SURROUND;
+
+ private static final SparseIntArray ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING;
+ private static final SparseIntArray ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING;
+ private static final SparseIntArray HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING;
+
+ static {
+ ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING = new SparseIntArray();
+ ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+ HearingAidProfile.DeviceSide.SIDE_INVALID, DeviceSide.SIDE_INVALID);
+ ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+ HearingAidProfile.DeviceSide.SIDE_LEFT, DeviceSide.SIDE_LEFT);
+ ASHA_DEVICE_SIDE_TO_INTERNAL_SIDE_MAPPING.put(
+ HearingAidProfile.DeviceSide.SIDE_RIGHT, DeviceSide.SIDE_RIGHT);
+
+ ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING = new SparseIntArray();
+ ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+ HearingAidProfile.DeviceMode.MODE_INVALID, DeviceMode.MODE_INVALID);
+ ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+ HearingAidProfile.DeviceMode.MODE_MONAURAL, DeviceMode.MODE_MONAURAL);
+ ASHA_DEVICE_MODE_TO_INTERNAL_MODE_MAPPING.put(
+ HearingAidProfile.DeviceMode.MODE_BINAURAL, DeviceMode.MODE_BINAURAL);
+
+ HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING = new SparseIntArray();
+ HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+ HapClientProfile.HearingAidType.TYPE_INVALID, DeviceMode.MODE_INVALID);
+ HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+ HapClientProfile.HearingAidType.TYPE_BINAURAL, DeviceMode.MODE_BINAURAL);
+ HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+ HapClientProfile.HearingAidType.TYPE_MONAURAL, DeviceMode.MODE_MONAURAL);
+ HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+ HapClientProfile.HearingAidType.TYPE_BANDED, DeviceMode.MODE_BANDED);
+ HAP_DEVICE_TYPE_TO_INTERNAL_MODE_MAPPING.put(
+ HapClientProfile.HearingAidType.TYPE_RFU, DeviceMode.MODE_INVALID);
+
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 4e9073c..a3c2e70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -345,25 +345,44 @@
Log.i(TAG, "Failed to connect " + mProfile + " device");
}
- if (getHearingAidProfile() != null &&
- mProfile instanceof HearingAidProfile &&
- (newState == BluetoothProfile.STATE_CONNECTED)) {
- final int side = getHearingAidProfile().getDeviceSide(cachedDevice.getDevice());
- final int mode = getHearingAidProfile().getDeviceMode(cachedDevice.getDevice());
- cachedDevice.setDeviceSide(side);
- cachedDevice.setDeviceMode(mode);
+ if (getHearingAidProfile() != null
+ && mProfile instanceof HearingAidProfile
+ && (newState == BluetoothProfile.STATE_CONNECTED)) {
// Check if the HiSyncID has being initialized
if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- cachedDevice.setHiSyncId(newHiSyncId);
+ final BluetoothDevice device = cachedDevice.getDevice();
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setAshaDeviceSide(getHearingAidProfile().getDeviceSide(device))
+ .setAshaDeviceMode(getHearingAidProfile().getDeviceMode(device))
+ .setHiSyncId(newHiSyncId);
+ cachedDevice.setHearingAidInfo(infoBuilder.build());
}
}
-
HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
}
+ final boolean isHapClientProfile = getHapClientProfile() != null
+ && mProfile instanceof HapClientProfile;
+ final boolean isLeAudioProfile = getLeAudioProfile() != null
+ && mProfile instanceof LeAudioProfile;
+ final boolean isHapClientOrLeAudioProfile = isHapClientProfile || isLeAudioProfile;
+ if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) {
+
+ // Checks if both profiles are connected to the device. Hearing aid info need
+ // to be retrieved from these profiles separately.
+ if (cachedDevice.isConnectedLeAudioHearingAidDevice()) {
+ final BluetoothDevice device = cachedDevice.getDevice();
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setLeAudioLocation(getLeAudioProfile().getAudioLocation(device))
+ .setHapDeviceType(getHapClientProfile().getHearingAidType(device));
+ cachedDevice.setHearingAidInfo(infoBuilder.build());
+ HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
+ }
+ }
+
if (getCsipSetCoordinatorProfile() != null
&& mProfile instanceof CsipSetCoordinatorProfile
&& newState == BluetoothProfile.STATE_CONNECTED) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 39875f7..96e64ea 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -755,28 +755,30 @@
@Test
public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() {
+ UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
ApplicationInfo appInfo = createApplicationInfo(PKG_1);
AppEntry primaryUserApp = createAppEntry(appInfo, 1);
- assertThat(primaryUserApp.shouldShowInPersonalTab(mContext, appInfo.uid)).isTrue();
+ assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
}
@Test
public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
+ UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.TIRAMISU);
// Create an app (and subsequent AppEntry) in a non-primary user profile.
ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1);
- assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(mContext, appInfo1.uid)).isFalse();
+ assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(um, appInfo1.uid)).isFalse();
}
@Test
public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() {
// Mark system user as parent for both profile users.
- ShadowUserManager shadowUserManager = Shadow
- .extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+ UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
+ ShadowUserManager shadowUserManager = Shadow.extract(um);
shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID,
CLONE_USER, 0);
shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2,
@@ -796,7 +798,7 @@
AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1);
AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2);
- assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(mContext, appInfo1.uid)).isTrue();
- assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(mContext, appInfo2.uid)).isFalse();
+ assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(um, appInfo1.uid)).isTrue();
+ assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(um, appInfo2.uid)).isFalse();
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 61802a8..f06623d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -327,8 +327,10 @@
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
- cachedDevice1.setHiSyncId(HISYNCID1);
- cachedDevice2.setHiSyncId(HISYNCID1);
+ cachedDevice1.setHearingAidInfo(
+ new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
+ cachedDevice2.setHearingAidInfo(
+ new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
cachedDevice1.setSubDevice(cachedDevice2);
// Call onDeviceUnpaired for the one in mCachedDevices.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 864725b..1f518ec 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -358,7 +358,7 @@
assertThat(mCachedDevice.getConnectionSummary()).isNull();
// Set device as Active for Hearing Aid and test connection state summary
- mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+ mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
@@ -403,7 +403,7 @@
// 1. Profile: {HEARING_AID, Connected, Active, Right ear}
// 2. Audio Manager: In Call
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
- mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
@@ -417,9 +417,9 @@
// Arrange:
// 1. Profile: {HEARING_AID, Connected, Active, Both ear}
// 2. Audio Manager: In Call
- mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
- mSubCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+ mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
mCachedDevice.setSubDevice(mSubCachedDevice);
mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
@@ -969,10 +969,10 @@
mCachedDevice.mRssi = RSSI_1;
mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
- mCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
+ mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
mSubCachedDevice.mRssi = RSSI_2;
mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
- mSubCachedDevice.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ mSubCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
mCachedDevice.setSubDevice(mSubCachedDevice);
mCachedDevice.switchSubDeviceContent();
@@ -980,13 +980,11 @@
assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
- assertThat(mCachedDevice.getDeviceSide()).isEqualTo(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
- assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(
- HearingAidProfile.DeviceSide.SIDE_LEFT);
+ assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
}
@Test
@@ -1080,7 +1078,7 @@
updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
- assertThat(mCachedDevice.isHearingAidDevice()).isTrue();
+ assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
}
@Test
@@ -1091,7 +1089,7 @@
updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
- assertThat(mCachedDevice.isHearingAidDevice()).isTrue();
+ assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
}
@Test
@@ -1104,6 +1102,17 @@
updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_DISCONNECTED);
updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
- assertThat(mCachedDevice.isHearingAidDevice()).isFalse();
+ assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
+ }
+
+ private HearingAidInfo getLeftAshaHearingAidInfo() {
+ return new HearingAidInfo.Builder()
+ .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
+ .build();
+ }
+ private HearingAidInfo getRightAshaHearingAidInfo() {
+ return new HearingAidInfo.Builder()
+ .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT)
+ .build();
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 611b0a4..470d8e0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -106,7 +107,7 @@
* deviceSide, deviceMode.
*/
@Test
- public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfos() {
+ public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfo() {
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
HearingAidProfile.DeviceMode.MODE_BINAURAL);
@@ -118,21 +119,22 @@
assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
- HearingAidProfile.DeviceMode.MODE_BINAURAL);
+ HearingAidInfo.DeviceMode.MODE_BINAURAL);
assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ HearingAidInfo.DeviceSide.SIDE_RIGHT);
}
/**
* Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned
*/
@Test
- public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHiSyncId() {
+ public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHearingAidInfo() {
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
BluetoothHearingAid.HI_SYNC_ID_INVALID);
+
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
- verify(mCachedDevice1, never()).setHiSyncId(anyLong());
+ verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
}
/**
@@ -140,9 +142,10 @@
*/
@Test
public void setSubDeviceIfNeeded_sameHiSyncId_setSubDevice() {
- mCachedDevice1.setHiSyncId(HISYNCID1);
- mCachedDevice2.setHiSyncId(HISYNCID1);
+ mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+ mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
@@ -153,9 +156,10 @@
*/
@Test
public void setSubDeviceIfNeeded_differentHiSyncId_notSetSubDevice() {
- mCachedDevice1.setHiSyncId(HISYNCID1);
- mCachedDevice2.setHiSyncId(HISYNCID2);
+ mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+ mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID2));
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
assertThat(mCachedDevice1.getSubDevice()).isNull();
@@ -276,9 +280,9 @@
assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
- HearingAidProfile.DeviceMode.MODE_BINAURAL);
+ HearingAidInfo.DeviceMode.MODE_BINAURAL);
assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ HearingAidInfo.DeviceSide.SIDE_RIGHT);
verify(mHearingAidDeviceManager).onHiSyncIdChanged(HISYNCID1);
}
@@ -389,11 +393,9 @@
@Test
public void onProfileConnectionStateChanged_disconnected_mainDevice_subDeviceConnected_switch()
{
- when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
- mCachedDevice1.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT);
- when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
+ mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
+ mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
when(mCachedDevice2.isConnected()).thenReturn(true);
- mCachedDevice2.setDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
mCachedDevice1.setSubDevice(mCachedDevice2);
@@ -404,10 +406,8 @@
assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice2);
assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice1);
- assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
- assertThat(mCachedDevice2.getDeviceSide()).isEqualTo(
- HearingAidProfile.DeviceSide.SIDE_LEFT);
+ assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+ assertThat(mCachedDevice2.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
verify(mCachedDevice1).refresh();
}
@@ -455,4 +455,18 @@
assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)).
isEqualTo(mCachedDevice1);
}
+
+ private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) {
+ return new HearingAidInfo.Builder()
+ .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
+ .setHiSyncId(hiSyncId)
+ .build();
+ }
+
+ private HearingAidInfo getRightAshaHearingAidInfo(long hiSyncId) {
+ return new HearingAidInfo.Builder()
+ .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
+ .setHiSyncId(hiSyncId)
+ .build();
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 01c0809..680a0a1 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -771,6 +771,80 @@
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.READ_BASAL_METABOLIC_RATE" />
+ <uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE" />
+ <uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
+ <uses-permission android:name="android.permission.health.READ_BODY_FAT" />
+ <uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.READ_BODY_WATER_MASS" />
+ <uses-permission android:name="android.permission.health.READ_BONE_MASS" />
+ <uses-permission android:name="android.permission.health.READ_CERVICAL_MUCUS" />
+ <uses-permission android:name="android.permission.health.READ_DISTANCE" />
+ <uses-permission android:name="android.permission.health.READ_ELEVATION_GAINED" />
+ <uses-permission android:name="android.permission.health.READ_EXERCISE" />
+ <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY" />
+ <uses-permission android:name="android.permission.health.READ_HEIGHT" />
+ <uses-permission android:name="android.permission.health.READ_HIP_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.READ_HYDRATION" />
+ <uses-permission android:name="android.permission.health.READ_LEAN_BODY_MASS" />
+ <uses-permission android:name="android.permission.health.READ_MENSTRUATION" />
+ <uses-permission android:name="android.permission.health.READ_NUTRITION" />
+ <uses-permission android:name="android.permission.health.READ_OVULATION_TEST" />
+ <uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION" />
+ <uses-permission android:name="android.permission.health.READ_POWER" />
+ <uses-permission android:name="android.permission.health.READ_RESPIRATORY_RATE" />
+ <uses-permission android:name="android.permission.health.READ_RESTING_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.READ_SEXUAL_ACTIVITY" />
+ <uses-permission android:name="android.permission.health.READ_SLEEP" />
+ <uses-permission android:name="android.permission.health.READ_SPEED" />
+ <uses-permission android:name="android.permission.health.READ_STEPS" />
+ <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.READ_VO2_MAX" />
+ <uses-permission android:name="android.permission.health.READ_WAIST_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.READ_WEIGHT" />
+ <uses-permission android:name="android.permission.health.READ_WHEELCHAIR_PUSHES" />
+ <uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.WRITE_BASAL_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.WRITE_BASAL_METABOLIC_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE" />
+ <uses-permission android:name="android.permission.health.WRITE_BLOOD_PRESSURE" />
+ <uses-permission android:name="android.permission.health.WRITE_BODY_FAT" />
+ <uses-permission android:name="android.permission.health.WRITE_BODY_TEMPERATURE" />
+ <uses-permission android:name="android.permission.health.WRITE_BODY_WATER_MASS" />
+ <uses-permission android:name="android.permission.health.WRITE_BONE_MASS" />
+ <uses-permission android:name="android.permission.health.WRITE_CERVICAL_MUCUS" />
+ <uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
+ <uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
+ <uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
+ <uses-permission android:name="android.permission.health.WRITE_FLOORS_CLIMBED" />
+ <uses-permission android:name="android.permission.health.WRITE_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_HEART_RATE_VARIABILITY" />
+ <uses-permission android:name="android.permission.health.WRITE_HEIGHT" />
+ <uses-permission android:name="android.permission.health.WRITE_HIP_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.WRITE_HYDRATION" />
+ <uses-permission android:name="android.permission.health.WRITE_LEAN_BODY_MASS" />
+ <uses-permission android:name="android.permission.health.WRITE_MENSTRUATION" />
+ <uses-permission android:name="android.permission.health.WRITE_NUTRITION" />
+ <uses-permission android:name="android.permission.health.WRITE_OVULATION_TEST" />
+ <uses-permission android:name="android.permission.health.WRITE_OXYGEN_SATURATION" />
+ <uses-permission android:name="android.permission.health.WRITE_POWER" />
+ <uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_RESTING_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.WRITE_SEXUAL_ACTIVITY" />
+ <uses-permission android:name="android.permission.health.WRITE_SLEEP" />
+ <uses-permission android:name="android.permission.health.WRITE_SPEED" />
+ <uses-permission android:name="android.permission.health.WRITE_STEPS" />
+ <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
+ <uses-permission android:name="android.permission.health.WRITE_VO2_MAX" />
+ <uses-permission android:name="android.permission.health.WRITE_WAIST_CIRCUMFERENCE" />
+ <uses-permission android:name="android.permission.health.WRITE_WEIGHT" />
+ <uses-permission android:name="android.permission.health.WRITE_WHEELCHAIR_PUSHES" />
+
<!-- Permission required for CTS test - ApplicationExemptionsTests -->
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index f7bcf1f..5df79e1 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -36,8 +36,29 @@
static_libs: [
"PluginCoreLib",
+ "androidx.core_core-animation-nodeps",
],
manifest: "AndroidManifest.xml",
kotlincflags: ["-Xjvm-default=all"],
}
+
+android_test {
+ name: "SystemUIAnimationLibTests",
+
+ static_libs: [
+ "SystemUIAnimationLib",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "testables",
+ ],
+ libs: [
+ "android.test.base",
+ ],
+ srcs: [
+ "**/*.java",
+ "**/*.kt",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+ test_suites: ["general-tests"],
+}
diff --git a/packages/SystemUI/animation/TEST_MAPPING b/packages/SystemUI/animation/TEST_MAPPING
new file mode 100644
index 0000000..3dc8510
--- /dev/null
+++ b/packages/SystemUI/animation/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "SystemUIAnimationLibTests"
+ }
+ ]
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 8063483..9dbb920 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -27,7 +27,10 @@
import android.view.animation.PathInterpolator;
/**
- * Utility class to receive interpolators from
+ * Utility class to receive interpolators from.
+ *
+ * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
+ * Please consider using the androidx dependencies featuring better testability altogether.
*/
public class Interpolators {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
new file mode 100644
index 0000000..8da87feb
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
@@ -0,0 +1,219 @@
+/*
+ * 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.animation;
+
+import android.graphics.Path;
+import android.util.MathUtils;
+
+import androidx.core.animation.AccelerateDecelerateInterpolator;
+import androidx.core.animation.AccelerateInterpolator;
+import androidx.core.animation.BounceInterpolator;
+import androidx.core.animation.DecelerateInterpolator;
+import androidx.core.animation.Interpolator;
+import androidx.core.animation.LinearInterpolator;
+import androidx.core.animation.PathInterpolator;
+
+/**
+ * Utility class to receive interpolators from. (androidx compatible version)
+ *
+ * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
+ * this class are also reflected in {@link Interpolators}.
+ *
+ * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
+ * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
+ * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
+ * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
+ * advancing the time).
+ */
+public class InterpolatorsAndroidX {
+
+ /*
+ * ============================================================================================
+ * Emphasized interpolators.
+ * ============================================================================================
+ */
+
+ /**
+ * The default emphasized interpolator. Used for hero / emphasized movement of content.
+ */
+ public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+ /**
+ * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+ * is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 0.8f, 0.15f);
+
+ /**
+ * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+ * is appearing e.g. when coming from off screen
+ */
+ public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+ 0.05f, 0.7f, 0.1f, 1f);
+
+
+ /*
+ * ============================================================================================
+ * Standard interpolators.
+ * ============================================================================================
+ */
+
+ /**
+ * The standard interpolator that should be used on every normal animation
+ */
+ public static final Interpolator STANDARD = new PathInterpolator(
+ 0.2f, 0f, 0f, 1f);
+
+ /**
+ * The standard accelerating interpolator that should be used on every regular movement of
+ * content that is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 1f, 1f);
+
+ /**
+ * The standard decelerating interpolator that should be used on every regular movement of
+ * content that is appearing e.g. when coming from off screen.
+ */
+ public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
+ 0f, 0f, 0f, 1f);
+
+ /*
+ * ============================================================================================
+ * Legacy
+ * ============================================================================================
+ */
+
+ /**
+ * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
+ */
+ public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+ /**
+ * The default legacy accelerating interpolator as defined in Material 1.
+ * Also known as FAST_OUT_LINEAR_IN.
+ */
+ public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+ /**
+ * The default legacy decelerating interpolator as defined in Material 1.
+ * Also known as LINEAR_OUT_SLOW_IN.
+ */
+ public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+ /**
+ * Linear interpolator. Often used if the interpolator is for different properties who need
+ * different interpolations.
+ */
+ public static final Interpolator LINEAR = new LinearInterpolator();
+
+ /*
+ * ============================================================================================
+ * Custom interpolators
+ * ============================================================================================
+ */
+
+ public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
+ public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
+ public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
+
+ /**
+ * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
+ * goes from 1 to 0 instead of 0 to 1).
+ */
+ public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
+ new PathInterpolator(0.8f, 0f, 0.6f, 1f);
+ public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
+ public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+ public static final Interpolator ACCELERATE = new AccelerateInterpolator();
+ public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
+ public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
+ public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
+ public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+ public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
+ 1.1f);
+ public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
+ 1);
+ public static final Interpolator BOUNCE = new BounceInterpolator();
+ /**
+ * For state transitions on the control panel that lives in GlobalActions.
+ */
+ public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+ 1.0f);
+
+ /**
+ * Interpolator to be used when animating a move based on a click. Pair with enough duration.
+ */
+ public static final Interpolator TOUCH_RESPONSE =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+ /**
+ * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
+ * goes from 1 to 0 instead of 0 to 1).
+ */
+ public static final Interpolator TOUCH_RESPONSE_REVERSE =
+ new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+
+ /*
+ * ============================================================================================
+ * Functions / Utilities
+ * ============================================================================================
+ */
+
+ /**
+ * Calculate the amount of overshoot using an exponential falloff function with desired
+ * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
+ * overshoot, retaining its acceleration.
+ *
+ * @param progress a progress value going from 0 to 1
+ * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
+ * value of the overall progress will be at 1.1.
+ * @param overshootStart the point in (0,1] where the result should reach 1
+ * @return the interpolated overshoot
+ */
+ public static float getOvershootInterpolation(float progress, float overshootAmount,
+ float overshootStart) {
+ if (overshootAmount == 0.0f || overshootStart == 0.0f) {
+ throw new IllegalArgumentException("Invalid values for overshoot");
+ }
+ float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
+ return MathUtils.max(0.0f,
+ (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
+ }
+
+ /**
+ * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
+ * starts immediately here, instead of first having a section of non-overshooting
+ *
+ * @param progress a progress value going from 0 to 1
+ */
+ public static float getOvershootInterpolation(float progress) {
+ return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
+ }
+
+ // Create the default emphasized interpolator
+ private static PathInterpolator createEmphasizedInterpolator() {
+ Path path = new Path();
+ // Doing the same as fast_out_extra_slow_in
+ path.moveTo(0f, 0f);
+ path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+ path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+ return new PathInterpolator(path);
+ }
+}
diff --git a/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
new file mode 100644
index 0000000..389eed0
--- /dev/null
+++ b/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
@@ -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 com.android.systemui.animation
+
+import androidx.test.filters.SmallTest
+import java.lang.reflect.Modifier
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class InterpolatorsAndroidXTest {
+
+ @Test
+ fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
+ assertEquals(
+ Interpolators::class.java.getPublicMethods(),
+ InterpolatorsAndroidX::class.java.getPublicMethods()
+ )
+ }
+
+ @Test
+ fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
+ assertEquals(
+ Interpolators::class.java.getPublicFields(),
+ InterpolatorsAndroidX::class.java.getPublicFields()
+ )
+ }
+
+ private fun <T> Class<T>.getPublicMethods() =
+ declaredMethods
+ .filter { Modifier.isPublic(it.modifiers) }
+ .map { it.toString().replace(name, "") }
+ .toSet()
+
+ private fun <T> Class<T>.getPublicFields() =
+ fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
+}
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
new file mode 100644
index 0000000..e850d68
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#1f1f1f"
+ android:pathData="M8,22V11L6,8V2H18V8L16,11V22ZM12,15.5Q11.375,15.5 10.938,15.062Q10.5,14.625 10.5,14Q10.5,13.375 10.938,12.938Q11.375,12.5 12,12.5Q12.625,12.5 13.062,12.938Q13.5,13.375 13.5,14Q13.5,14.625 13.062,15.062Q12.625,15.5 12,15.5ZM8,5H16V4H8ZM16,7H8V7.4L10,10.4V20H14V10.4L16,7.4ZM12,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
new file mode 100644
index 0000000..91b9ae5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#1f1f1f"
+ android:pathData="M6,5V2H18V5ZM12,15.5Q12.625,15.5 13.062,15.062Q13.5,14.625 13.5,14Q13.5,13.375 13.062,12.938Q12.625,12.5 12,12.5Q11.375,12.5 10.938,12.938Q10.5,13.375 10.5,14Q10.5,14.625 10.938,15.062Q11.375,15.5 12,15.5ZM8,22V11L6,8V7H18V8L16,11V22Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 2b7bdc2..c772c96 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,7 +27,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- androidprv:layout_maxWidth="@dimen/keyguard_security_width"
+ androidprv:layout_maxWidth="@dimen/biometric_auth_pattern_view_max_size"
android:layout_gravity="center_horizontal|bottom"
android:clipChildren="false"
android:clipToPadding="false">
diff --git a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml b/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
deleted file mode 100644
index a3c37e4..0000000
--- a/packages/SystemUI/res-keyguard/values-sw540dp-port/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/dimens.xml
-**
-** Copyright 2013, 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>
- <!-- Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
- <dimen name="keyguard_security_height">550dp</dimen>
-</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
index 1dc61c5..b7a1bb4 100644
--- a/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/dimens.xml
@@ -17,10 +17,5 @@
*/
-->
<resources>
-
- <!-- Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
- <dimen name="keyguard_security_height">470dp</dimen>
-
<dimen name="widget_big_font_size">100dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 3861d98..e6593b1 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -29,9 +29,6 @@
(includes 2x keyguard_security_view_top_margin) -->
<dimen name="keyguard_security_height">420dp</dimen>
- <!-- Max Height of the sliding KeyguardSecurityContainer
- (includes 2x keyguard_security_view_top_margin) -->
-
<!-- pin/password field max height -->
<dimen name="keyguard_password_height">80dp</dimen>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 6e0e38b..88f138f 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -71,8 +71,8 @@
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
android:layout_gravity="center"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
<TextView
android:id="@+id/error"
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 891c6af..81ca3718 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -67,8 +67,8 @@
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
android:layout_gravity="center"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
<TextView
android:id="@+id/error"
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 49ef330..fff2544 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -40,6 +40,10 @@
<dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">116dp</dimen>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
<dimen name="global_actions_power_dialog_item_height">130dp</dimen>
<dimen name="global_actions_power_dialog_item_bottom_margin">35dp</dimen>
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index aefd998..a0e721e 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -29,11 +29,11 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">320dp</item>
- <item name="android:maxWidth">320dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:paddingHorizontal">32dp</item>
<item name="android:paddingVertical">20dp</item>
</style>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 16152f8..08e1bf2 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -78,9 +78,6 @@
<color name="biometric_dialog_accent">@color/material_dynamic_primary70</color>
<color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
- <!-- UDFPS colors -->
- <color name="udfps_enroll_icon">#7DA7F1</color>
-
<color name="GM2_green_500">#FF41Af6A</color>
<color name="GM2_blue_500">#5195EA</color>
<color name="GM2_red_500">#E25142</color>
@@ -103,4 +100,13 @@
<color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
<color name="people_tile_background">@color/material_dynamic_secondary20</color>
+
+ <!-- UDFPS colors -->
+ <color name="udfps_enroll_icon">#7DA7F1</color>
+ <color name="udfps_moving_target_fill">#475670</color>
+ <!-- 50% of udfps_moving_target_fill-->
+ <color name="udfps_moving_target_fill_error">#80475670</color>
+ <color name="udfps_enroll_progress">#7DA7F1</color>
+ <color name="udfps_enroll_progress_help">#607DA7F1</color>
+ <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
</resources>
diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml
index 65ca70b..03365b3 100644
--- a/packages/SystemUI/res/values-sw360dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw360dp/dimens.xml
@@ -25,5 +25,8 @@
<!-- Home Controls -->
<dimen name="global_actions_side_margin">12dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
new file mode 100644
index 0000000..1e26a69
--- /dev/null
+++ b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">248dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 78279ca..96af3c1 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -24,5 +24,8 @@
<!-- Home Controls -->
<dimen name="global_actions_side_margin">16dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
new file mode 100644
index 0000000..c4d9b9b
--- /dev/null
+++ b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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>
+ <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+ <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index 7da47e5..ff6e005 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -27,4 +27,6 @@
<dimen name="global_actions_grid_item_side_margin">12dp</dimen>
<dimen name="global_actions_grid_item_height">72dp</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 8148d3d..5ca2b43 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -16,16 +16,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">120dp</item>
- <item name="android:paddingVertical">40dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 771de08..41d931d 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -24,16 +24,6 @@
<item name="android:layout_gravity">top</item>
</style>
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">180dp</item>
- <item name="android:paddingVertical">80dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 599bf30..9bc0dde 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -92,4 +92,6 @@
<dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen>
<dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index f9ed67d..d9406d3 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -16,16 +16,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">120dp</item>
- <item name="android:paddingVertical">40dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">16dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 78d299c..41d931d 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -24,16 +24,6 @@
<item name="android:layout_gravity">top</item>
</style>
- <style name="AuthCredentialPatternContainerStyle">
- <item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:paddingHorizontal">240dp</item>
- <item name="android:paddingVertical">120dp</item>
- </style>
-
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">24dp</item>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 0705017..927059a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -22,5 +22,8 @@
<dimen name="controls_padding_horizontal">75dp</dimen>
<dimen name="large_screen_shade_header_height">56dp</dimen>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw800dp/dimens.xml b/packages/SystemUI/res/values-sw800dp/dimens.xml
new file mode 100644
index 0000000..0d82217
--- /dev/null
+++ b/packages/SystemUI/res/values-sw800dp/dimens.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.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 44ba3f6..5b6c9d3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -209,5 +209,15 @@
<attr name="permissionGrantButtonTopStyle" format="reference"/>
<attr name="permissionGrantButtonBottomStyle" format="reference"/>
</declare-styleable>
+
+ <declare-styleable name="BiometricsEnrollView">
+ <attr name="biometricsEnrollStyle" format="reference" />
+ <attr name="biometricsEnrollIcon" format="reference|color" />
+ <attr name="biometricsMovingTargetFill" format="reference|color" />
+ <attr name="biometricsMovingTargetFillError" format="reference|color" />
+ <attr name="biometricsEnrollProgress" format="reference|color" />
+ <attr name="biometricsEnrollProgressHelp" format="reference|color" />
+ <attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 84cab6c..8ee39dd 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -134,12 +134,12 @@
<color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
<!-- UDFPS colors -->
- <color name="udfps_enroll_icon">#7DA7F1</color>
- <color name="udfps_moving_target_fill">#475670</color>
+ <color name="udfps_enroll_icon">#699FF3</color>
+ <color name="udfps_moving_target_fill">#C2D7F7</color>
<!-- 50% of udfps_moving_target_fill-->
- <color name="udfps_moving_target_fill_error">#80475670</color>
- <color name="udfps_enroll_progress">#7DA7F1</color>
- <color name="udfps_enroll_progress_help">#607DA7F1</color>
+ <color name="udfps_moving_target_fill_error">#80C2D7F7</color>
+ <color name="udfps_enroll_progress">#699FF3</color>
+ <color name="udfps_enroll_progress_help">#70699FF3</color>
<color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
<!-- Floating overlay actions -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9f29c5b..569b661 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -959,6 +959,10 @@
<!-- Biometric Auth Credential values -->
<dimen name="biometric_auth_icon_size">48dp</dimen>
+ <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
+ <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+ <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
<!-- Starting text size in sp of batteryLevel for wireless charging animation -->
<item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
0
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fe4f639..aafa47f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -251,11 +251,12 @@
<style name="AuthCredentialPatternContainerStyle">
<item name="android:gravity">center</item>
- <item name="android:maxHeight">420dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">200dp</item>
- <item name="android:minWidth">200dp</item>
- <item name="android:padding">20dp</item>
+ <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+ <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+ <item name="android:paddingHorizontal">32dp</item>
+ <item name="android:paddingVertical">20dp</item>
</style>
<style name="AuthCredentialPinPasswordContainerStyle">
@@ -314,6 +315,10 @@
<!-- Needed for MediaRoute chooser dialog -->
<item name="*android:isLightTheme">false</item>
+
+ <!-- Biometrics enroll color style -->
+ <item name="biometricsEnrollStyle">@style/BiometricsEnrollStyle</item>
+
</style>
<style name="Theme.SystemUI.LightWallpaper">
@@ -1281,7 +1286,6 @@
<item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
</style>
-
<!-- The style for log access consent dialog -->
<style name="LogAccessDialogTheme" parent="@style/Theme.SystemUI.Dialog.Alert">
<item name="permissionGrantButtonTopStyle">@style/PermissionGrantButtonTop</item>
@@ -1321,4 +1325,13 @@
<item name="android:layout_marginBottom">2dp</item>
<item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
</style>
+
+ <style name="BiometricsEnrollStyle">
+ <item name="biometricsEnrollIcon">@color/udfps_enroll_icon</item>
+ <item name="biometricsMovingTargetFill">@color/udfps_moving_target_fill</item>
+ <item name="biometricsMovingTargetFillError">@color/udfps_moving_target_fill_error</item>
+ <item name="biometricsEnrollProgress">@color/udfps_enroll_progress</item>
+ <item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
+ <item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index b2a2a67..b962cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -107,6 +107,8 @@
if (shouldAnimateForTransition(lastState, newState)) {
iconView.playAnimation()
iconViewOverlay.playAnimation()
+ } else if (lastState == STATE_IDLE && newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+ iconView.playAnimation()
}
LottieColorUtils.applyDynamicColors(context, iconView)
LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index 1e35958..3e1c4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
@@ -28,6 +29,7 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
+import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.NonNull;
@@ -68,25 +70,29 @@
private boolean mShouldShowTipHint = false;
private boolean mShouldShowEdgeHint = false;
- UdfpsEnrollDrawable(@NonNull Context context) {
+ private int mEnrollIcon;
+ private int mMovingTargetFill;
+
+ UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context);
+ loadResources(context, attrs);
mSensorOutlinePaint = new Paint(0 /* flags */);
mSensorOutlinePaint.setAntiAlias(true);
- mSensorOutlinePaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
+ mSensorOutlinePaint.setColor(mMovingTargetFill);
mSensorOutlinePaint.setStyle(Paint.Style.FILL);
mBlueFill = new Paint(0 /* flags */);
mBlueFill.setAntiAlias(true);
- mBlueFill.setColor(context.getColor(R.color.udfps_moving_target_fill));
+ mBlueFill.setColor(mMovingTargetFill);
mBlueFill.setStyle(Paint.Style.FILL);
mMovingTargetFpIcon = context.getResources()
.getDrawable(R.drawable.ic_kg_fingerprint, null);
- mMovingTargetFpIcon.setTint(context.getColor(R.color.udfps_enroll_icon));
+ mMovingTargetFpIcon.setTint(mEnrollIcon);
mMovingTargetFpIcon.mutate();
- getFingerprintDrawable().setTint(context.getColor(R.color.udfps_enroll_icon));
+ getFingerprintDrawable().setTint(mEnrollIcon);
mTargetAnimListener = new Animator.AnimatorListener() {
@Override
@@ -105,6 +111,16 @@
};
}
+ void loadResources(Context context, @Nullable AttributeSet attrs) {
+ final TypedArray ta = context.obtainStyledAttributes(attrs,
+ R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
+ R.style.BiometricsEnrollStyle);
+ mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
+ mMovingTargetFill = ta.getColor(
+ R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
+ ta.recycle();
+ }
+
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
mEnrollHelper = helper;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index af7e0b6..66a8424 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
@@ -26,6 +27,7 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.util.AttributeSet;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -93,17 +95,25 @@
@Nullable private ValueAnimator mCheckmarkAnimator;
@NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
- public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
+ private int mMovingTargetFill;
+ private int mMovingTargetFillError;
+ private int mEnrollProgress;
+ private int mEnrollProgressHelp;
+ private int mEnrollProgressHelpWithTalkback;
+
+ public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
mContext = context;
+
+ loadResources(context, attrs);
mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
- mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+ mProgressColor = mEnrollProgress;
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
- mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
+ mOnFirstBucketFailedColor = mMovingTargetFillError;
if (!mIsAccessibilityEnabled) {
- mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+ mHelpColor = mEnrollProgressHelp;
} else {
- mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
+ mHelpColor = mEnrollProgressHelpWithTalkback;
}
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
mCheckmarkDrawable.mutate();
@@ -111,7 +121,7 @@
mBackgroundPaint = new Paint();
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
- mBackgroundPaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
+ mBackgroundPaint.setColor(mMovingTargetFill);
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setStyle(Paint.Style.STROKE);
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
@@ -147,6 +157,23 @@
};
}
+ void loadResources(Context context, @Nullable AttributeSet attrs) {
+ final TypedArray ta = context.obtainStyledAttributes(attrs,
+ R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
+ R.style.BiometricsEnrollStyle);
+ mMovingTargetFill = ta.getColor(
+ R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
+ mMovingTargetFillError = ta.getColor(
+ R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
+ mEnrollProgress = ta.getColor(
+ R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
+ mEnrollProgressHelp = ta.getColor(
+ R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
+ mEnrollProgressHelpWithTalkback = ta.getColor(
+ R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
+ ta.recycle();
+ }
+
void onEnrollmentProgress(int remaining, int totalSteps) {
mAfterFirstTouch = true;
updateState(remaining, totalSteps, false /* showingHelp */);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 87be42c..1cc4141 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -47,8 +47,8 @@
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
- mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
- mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context);
+ mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
+ mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
mHandler = new Handler(Looper.getMainLooper());
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 4dfcd63..66e5d7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -30,6 +30,7 @@
import android.service.controls.ControlsProviderService
import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo
+import com.android.systemui.R
import java.util.Objects
class ControlsServiceInfo(
@@ -59,7 +60,8 @@
* instead of using the controls rendered by SystemUI.
*
* The activity must be in the same package, exported, enabled and protected by the
- * [Manifest.permission.BIND_CONTROLS] permission.
+ * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in
+ * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
*/
var panelActivity: ComponentName? = null
private set
@@ -70,6 +72,9 @@
fun resolvePanelActivity() {
if (resolved) return
resolved = true
+ val validPackages = context.resources
+ .getStringArray(R.array.config_controlsPreferredPackages)
+ if (componentName.packageName !in validPackages) return
panelActivity = _panelActivity?.let {
val resolveInfos = mPm.queryIntentActivitiesAsUser(
Intent().setComponent(it),
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e2f86bd..aa6c619 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -366,6 +366,11 @@
@JvmField
val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false)
+ // TODO(b/255854141): Tracking Bug
+ @JvmField
+ val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
+ unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = false)
+
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
@JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
@@ -386,7 +391,7 @@
// 1600 - accessibility
@JvmField
val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
- unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
+ unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations", teamfood = true)
// 1700 - clipboard
@JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index f5220b8..73dbeab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -25,6 +25,7 @@
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
const val CAMERA = "camera"
+ const val FLASHLIGHT = "flashlight"
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
new file mode 100644
index 0000000..49527d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.data.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+@SysUISingleton
+class FlashlightQuickAffordanceConfig @Inject constructor(
+ @Application private val context: Context,
+ private val flashlightController: FlashlightController,
+) : KeyguardQuickAffordanceConfig {
+
+ private sealed class FlashlightState {
+
+ abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState
+
+ object On: FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.ic_flashlight_on,
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ),
+ ActivationState.Active
+ )
+ }
+
+ object OffAvailable: FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.ic_flashlight_off,
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ),
+ ActivationState.Inactive
+ )
+ }
+
+ object Unavailable: FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.FLASHLIGHT
+
+ override val pickerName: String
+ get() = context.getString(R.string.quick_settings_flashlight_label)
+
+ override val pickerIconResourceId: Int
+ get() = if (flashlightController.isEnabled) {
+ R.drawable.ic_flashlight_on
+ } else {
+ R.drawable.ic_flashlight_off
+ }
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val flashlightCallback = object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {
+ trySendWithFailureLogging(
+ if (enabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ },
+ TAG
+ )
+ }
+
+ override fun onFlashlightError() {
+ trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG)
+ }
+
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {
+ trySendWithFailureLogging(
+ if (!available) {
+ FlashlightState.Unavailable.toLockScreenState()
+ } else {
+ if (flashlightController.isEnabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ }
+ },
+ TAG
+ )
+ }
+ }
+
+ flashlightController.addCallback(flashlightCallback)
+
+ awaitClose {
+ flashlightController.removeCallback(flashlightCallback)
+ }
+ }
+
+ override fun onTriggered(expandable: Expandable?):
+ KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ flashlightController
+ .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+ if (flashlightController.isAvailable) {
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+
+ companion object {
+ private const val TAG = "FlashlightQuickAffordanceConfig"
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index f7225a2..3013227c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -26,6 +26,7 @@
@Provides
@ElementsIntoSet
fun quickAffordanceConfigs(
+ flashlight: FlashlightQuickAffordanceConfig,
home: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
@@ -33,6 +34,7 @@
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
camera,
+ flashlight,
home,
quickAccessWallet,
qrCodeScanner,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index f9e341c..d6e29e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -16,9 +16,9 @@
package com.android.systemui.log
-import android.app.ActivityManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
@@ -29,15 +29,6 @@
private val dumpManager: DumpManager,
private val logcatEchoTracker: LogcatEchoTracker
) {
- /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
- private fun adjustMaxSize(requestedMaxSize: Int): Int {
- return if (ActivityManager.isLowRamDeviceStatic()) {
- minOf(requestedMaxSize, 20) /* low ram max log size*/
- } else {
- requestedMaxSize
- }
- }
-
@JvmOverloads
fun create(
name: String,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
new file mode 100644
index 0000000..619eac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.log
+
+import android.app.ActivityManager
+
+class LogBufferHelper {
+ companion object {
+ /** If necessary, returns a limited maximum size for low ram (Go) devices */
+ fun adjustMaxSize(requestedMaxSize: Int): Int {
+ return if (ActivityManager.isLowRamDeviceStatic()) {
+ minOf(requestedMaxSize, 20) /* low ram max log size*/
+ } else {
+ requestedMaxSize
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
new file mode 100644
index 0000000..c27bfa3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.log.table
+
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * An interface that enables logging the difference between values in table format.
+ *
+ * Many objects that we want to log are data-y objects with a collection of fields. When logging
+ * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
+ * highlight changes in individual fields.
+ *
+ * See [TableLogBuffer].
+ */
+interface Diffable<T> {
+ /**
+ * Finds the differences between [prevVal] and [this] and logs those diffs to [row].
+ *
+ * Each implementer should determine which individual fields have changed between [prevVal] and
+ * [this], and only log the fields that have actually changed. This helps save buffer space.
+ *
+ * For example, if:
+ * - prevVal = Object(val1=100, val2=200, val3=300)
+ * - this = Object(val1=100, val2=200, val3=333)
+ *
+ * Then only the val3 change should be logged.
+ */
+ fun logDiffs(prevVal: T, row: TableRowLogger)
+}
+
+/**
+ * Each time the flow is updated with a new value, logs the differences between the previous value
+ * and the new value to the given [tableLogBuffer].
+ *
+ * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged.
+ */
+fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ initialValue: T,
+): Flow<T> {
+ return this.pairwiseBy(initialValue) { prevVal, newVal ->
+ tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
+ newVal
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
new file mode 100644
index 0000000..68c297f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.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.systemui.log.table
+
+/**
+ * A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable.
+ *
+ * Each message represents a change to exactly 1 type, specified by [DataType].
+ */
+data class TableChange(
+ var timestamp: Long = 0,
+ var columnPrefix: String = "",
+ var columnName: String = "",
+ var type: DataType = DataType.EMPTY,
+ var bool: Boolean = false,
+ var int: Int = 0,
+ var str: String? = null,
+) {
+ /** Resets to default values so that the object can be recycled. */
+ fun reset(timestamp: Long, columnPrefix: String, columnName: String) {
+ this.timestamp = timestamp
+ this.columnPrefix = columnPrefix
+ this.columnName = columnName
+ this.type = DataType.EMPTY
+ this.bool = false
+ this.int = 0
+ this.str = null
+ }
+
+ /** Sets this to store a string change. */
+ fun set(value: String?) {
+ type = DataType.STRING
+ str = value
+ }
+
+ /** Sets this to store a boolean change. */
+ fun set(value: Boolean) {
+ type = DataType.BOOLEAN
+ bool = value
+ }
+
+ /** Sets this to store an int change. */
+ fun set(value: Int) {
+ type = DataType.INT
+ int = value
+ }
+
+ /** Returns true if this object has a change. */
+ fun hasData(): Boolean {
+ return columnName.isNotBlank() && type != DataType.EMPTY
+ }
+
+ fun getName(): String {
+ return if (columnPrefix.isNotBlank()) {
+ "$columnPrefix.$columnName"
+ } else {
+ columnName
+ }
+ }
+
+ fun getVal(): String {
+ return when (type) {
+ DataType.EMPTY -> null
+ DataType.STRING -> str
+ DataType.INT -> int
+ DataType.BOOLEAN -> bool
+ }.toString()
+ }
+
+ enum class DataType {
+ STRING,
+ BOOLEAN,
+ INT,
+ EMPTY,
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
new file mode 100644
index 0000000..429637a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.log.table
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import java.text.SimpleDateFormat
+import java.util.Locale
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A logger that logs changes in table format.
+ *
+ * Some parts of System UI maintain a lot of pieces of state at once.
+ * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
+ *
+ * - 10-10 10:10:10.456: state2 updated to newVal2
+ * - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
+ * - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
+ * - 10-10 10:11:05.123: state1 updated to newVal1
+ * - 10-10 10:11:06.000: stateN updated to StateN(val1=false, val2=3)
+ *
+ * However, it can sometimes be more useful to view the state changes in table format:
+ *
+ * - timestamp--------- | state1- | state2- | ... | stateN.val1 | stateN.val2
+ * - -------------------------------------------------------------------------
+ * - 10-10 10:10:10.123 | val1--- | val2--- | ... | false------ | 0-----------
+ * - 10-10 10:10:10.456 | val1--- | newVal2 | ... | false------ | 0-----------
+ * - 10-10 10:11:00.000 | val1--- | newVal2 | ... | true------- | 1-----------
+ * - 10-10 10:11:02.123 | val1--- | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:05.123 | newVal1 | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:06.000 | newVal1 | newVal2 | ... | false------ | 3-----------
+ *
+ * This class enables easy logging of the state changes in both change event format and table
+ * format.
+ *
+ * This class also enables easy logging of states that are a collection of fields. For example,
+ * stateN in the above example consists of two fields -- val1 and val2. It's useful to put each
+ * field into its own column so that ABT (Android Bug Tool) can easily highlight changes to
+ * individual fields.
+ *
+ * How it works:
+ *
+ * 1) Create an instance of this buffer via [TableLogBufferFactory].
+ *
+ * 2) For any states being logged, implement [Diffable]. Implementing [Diffable] allows the state to
+ * only log the fields that have *changed* since the previous update, instead of always logging all
+ * fields.
+ *
+ * 3) Each time a change in a state happens, call [logDiffs]. If your state is emitted using a
+ * [Flow], you should use the [logDiffsForTable] extension function to automatically log diffs any
+ * time your flow emits a new value.
+ *
+ * When a dump occurs, there will be two dumps:
+ *
+ * 1) The change events under the dumpable name "$name-changes".
+ *
+ * 2) This class will coalesce all the diffs into a table format and log them under the dumpable
+ * name "$name-table".
+ *
+ * @param maxSize the maximum size of the buffer. Must be > 0.
+ */
+class TableLogBuffer(
+ maxSize: Int,
+ private val name: String,
+ private val systemClock: SystemClock,
+) {
+ init {
+ if (maxSize <= 0) {
+ throw IllegalArgumentException("maxSize must be > 0")
+ }
+ }
+
+ private val buffer = RingBuffer(maxSize) { TableChange() }
+
+ // A [TableRowLogger] object, re-used each time [logDiffs] is called.
+ // (Re-used to avoid object allocation.)
+ private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
+
+ /**
+ * Log the differences between [prevVal] and [newVal].
+ *
+ * The [newVal] object's method [Diffable.logDiffs] will be used to fetch the diffs.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged. This
+ * ensures that all the columns related to the same state object will be grouped together in the
+ * table.
+ */
+ @Synchronized
+ fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) {
+ val row = tempRow
+ row.timestamp = systemClock.currentTimeMillis()
+ row.columnPrefix = columnPrefix
+ newVal.logDiffs(prevVal, row)
+ }
+
+ // Keep these individual [logChange] methods private (don't let clients give us their own
+ // timestamps.)
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ // TODO(b/259454430): Add additional change types here.
+
+ @Synchronized
+ private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
+ val tableChange = buffer.advance()
+ tableChange.reset(timestamp, prefix, columnName)
+ return tableChange
+ }
+
+ /**
+ * Registers this buffer as dumpables in [dumpManager]. Must be called for the table to be
+ * dumped.
+ *
+ * This will be automatically called in [TableLogBufferFactory.create].
+ */
+ fun registerDumpables(dumpManager: DumpManager) {
+ dumpManager.registerNormalDumpable("$name-changes", changeDumpable)
+ dumpManager.registerNormalDumpable("$name-table", tableDumpable)
+ }
+
+ private val changeDumpable = Dumpable { pw, _ -> dumpChanges(pw) }
+ private val tableDumpable = Dumpable { pw, _ -> dumpTable(pw) }
+
+ /** Dumps the list of [TableChange] objects. */
+ @Synchronized
+ @VisibleForTesting
+ fun dumpChanges(pw: PrintWriter) {
+ for (i in 0 until buffer.size) {
+ buffer[i].dump(pw)
+ }
+ }
+
+ /** Dumps an individual [TableChange]. */
+ private fun TableChange.dump(pw: PrintWriter) {
+ if (!this.hasData()) {
+ return
+ }
+ val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
+ pw.print(formattedTimestamp)
+ pw.print(" ")
+ pw.print(this.getName())
+ pw.print("=")
+ pw.print(this.getVal())
+ pw.println()
+ }
+
+ /**
+ * Coalesces all the [TableChange] objects into a table of values of time and dumps the table.
+ */
+ // TODO(b/259454430): Since this is an expensive process, it could cause the bug report dump to
+ // fail and/or not dump anything else. We should move this processing to ABT (Android Bug
+ // Tool), where we have unlimited time to process.
+ @Synchronized
+ @VisibleForTesting
+ fun dumpTable(pw: PrintWriter) {
+ val messages = buffer.iterator().asSequence().toList()
+
+ if (messages.isEmpty()) {
+ return
+ }
+
+ // Step 1: Create list of column headers
+ val headerSet = mutableSetOf<String>()
+ messages.forEach { headerSet.add(it.getName()) }
+ val headers: MutableList<String> = headerSet.toList().sorted().toMutableList()
+ headers.add(0, "timestamp")
+
+ // Step 2: Create a list with the current values for each column. Will be updated with each
+ // change.
+ val currentRow: MutableList<String> = MutableList(headers.size) { DEFAULT_COLUMN_VALUE }
+
+ // Step 3: For each message, make the correct update to [currentRow] and save it to [rows].
+ val columnIndices: Map<String, Int> =
+ headers.mapIndexed { index, headerName -> headerName to index }.toMap()
+ val allRows = mutableListOf<List<String>>()
+
+ messages.forEach {
+ if (!it.hasData()) {
+ return@forEach
+ }
+
+ val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(it.timestamp)
+ if (formattedTimestamp != currentRow[0]) {
+ // The timestamp has updated, so save the previous row and continue to the next row
+ allRows.add(currentRow.toList())
+ currentRow[0] = formattedTimestamp
+ }
+ val columnIndex = columnIndices[it.getName()]!!
+ currentRow[columnIndex] = it.getVal()
+ }
+ // Add the last row
+ allRows.add(currentRow.toList())
+
+ // Step 4: Dump the rows
+ DumpsysTableLogger(
+ name,
+ headers,
+ allRows,
+ )
+ .printTableData(pw)
+ }
+
+ /**
+ * A private implementation of [TableRowLogger].
+ *
+ * Used so that external clients can't modify [timestamp].
+ */
+ private class TableRowLoggerImpl(
+ var timestamp: Long,
+ var columnPrefix: String,
+ val tableLogBuffer: TableLogBuffer,
+ ) : TableRowLogger {
+ /** Logs a change to a string value. */
+ override fun logChange(columnName: String, value: String?) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+
+ /** Logs a change to a boolean value. */
+ override fun logChange(columnName: String, value: Boolean) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+
+ /** Logs a change to an int value. */
+ override fun logChange(columnName: String, value: Int) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+ }
+}
+
+val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+private const val DEFAULT_COLUMN_VALUE = "UNKNOWN"
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
new file mode 100644
index 0000000..f1f906f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.log.table
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+@SysUISingleton
+class TableLogBufferFactory
+@Inject
+constructor(
+ private val dumpManager: DumpManager,
+ private val systemClock: SystemClock,
+) {
+ fun create(
+ name: String,
+ maxSize: Int,
+ ): TableLogBuffer {
+ val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
+ tableBuffer.registerDumpables(dumpManager)
+ return tableBuffer
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
new file mode 100644
index 0000000..a7ba13b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
@@ -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.systemui.log.table
+
+/**
+ * A class that logs a row to [TableLogBuffer].
+ *
+ * Objects that implement [Diffable] will receive an instance of this class, and can log any changes
+ * to individual fields using the [logChange] methods. All logged changes will be associated with
+ * the same timestamp.
+ */
+interface TableRowLogger {
+ /** Logs a change to a string value. */
+ fun logChange(columnName: String, value: String?)
+
+ /** Logs a change to a boolean value. */
+ fun logChange(columnName: String, value: Boolean)
+
+ /** Logs a change to an int value. */
+ fun logChange(columnName: String, value: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index cbb670e..b252be1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -799,6 +799,16 @@
}
if (
+ desiredLocation == LOCATION_QS &&
+ previousLocation == LOCATION_LOCKSCREEN &&
+ statusbarState == StatusBarState.SHADE
+ ) {
+ // This is an invalid transition, can happen when tapping on home control and the UMO
+ // while being on landscape orientation in tablet.
+ return false
+ }
+
+ if (
statusbarState == StatusBarState.KEYGUARD &&
(currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN)
) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 8609e4a..10d31ea 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -972,13 +972,8 @@
if (imageData.uri != null) {
if (!imageData.owner.equals(Process.myUserHandle())) {
- // TODO: Handle non-primary user ownership (e.g. Work Profile)
- // This image is owned by another user. Special treatment will be
- // required in the UI (badging) as well as sending intents which can
- // correctly forward those URIs on to be read (actions).
-
- Log.d(TAG, "*** Screenshot saved to a non-primary user ("
- + imageData.owner + ") as " + imageData.uri);
+ Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
+ + imageData.uri);
}
mScreenshotHandler.post(() -> {
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
@@ -1059,6 +1054,11 @@
R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
+ mPackageName);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 31e4464..5e47d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -19,6 +19,7 @@
import android.annotation.IdRes
import android.app.StatusBarManager
import android.content.res.Configuration
+import android.os.Bundle
import android.os.Trace
import android.os.Trace.TRACE_TAG_APP
import android.util.Pair
@@ -34,6 +35,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -53,6 +56,7 @@
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.VariableDateView
import com.android.systemui.statusbar.policy.VariableDateViewController
@@ -89,7 +93,8 @@
private val dumpManager: DumpManager,
private val featureFlags: FeatureFlags,
private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
- private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+ private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
+ private val demoModeController: DemoModeController
) : ViewController<View>(header), Dumpable {
companion object {
@@ -126,7 +131,7 @@
private lateinit var qsCarrierGroupController: QSCarrierGroupController
private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
- private val clock: TextView = header.findViewById(R.id.clock)
+ private val clock: Clock = header.findViewById(R.id.clock)
private val date: TextView = header.findViewById(R.id.date)
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
@@ -212,6 +217,14 @@
view.onApplyWindowInsets(insets)
}
+ private val demoModeReceiver = object : DemoMode {
+ override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+ override fun dispatchDemoCommand(command: String, args: Bundle) =
+ clock.dispatchDemoCommand(command, args)
+ override fun onDemoModeStarted() = clock.onDemoModeStarted()
+ override fun onDemoModeFinished() = clock.onDemoModeFinished()
+ }
+
private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
override fun onChipVisibilityRefreshed(visible: Boolean) {
if (header is MotionLayout) {
@@ -300,6 +313,7 @@
dumpManager.registerDumpable(this)
configurationController.addCallback(configurationControllerListener)
+ demoModeController.addCallback(demoModeReceiver)
updateVisibility()
updateTransition()
@@ -309,6 +323,7 @@
privacyIconsController.chipVisibilityListener = null
dumpManager.unregisterDumpable(this::class.java.simpleName)
configurationController.removeCallback(configurationControllerListener)
+ demoModeController.removeCallback(demoModeReceiver)
}
fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -521,4 +536,7 @@
updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
}
}
+
+ @VisibleForTesting
+ internal fun simulateViewDetached() = this.onViewDetached()
}
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 be08183..01ca667 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -122,6 +122,7 @@
ActivityOptions options = getDefaultActivityOptions(animationAdapter);
options.setLaunchDisplayId(displayId);
options.setCallerDisplayId(displayId);
+ options.setPendingIntentBackgroundActivityLaunchAllowed(true);
return options.toBundle();
}
@@ -145,6 +146,7 @@
: ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime);
options.setLaunchDisplayId(displayId);
options.setCallerDisplayId(displayId);
+ options.setPendingIntentBackgroundActivityLaunchAllowed(true);
return options.toBundle();
}
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 5efd460..32ea8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -977,6 +977,11 @@
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy.init();
+ // Based on teamfood flag, turn predictive back dispatch on at runtime.
+ if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+ mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+ }
+
mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
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 fcd1b8a..0662fb3 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
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -32,6 +35,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
abstract class StatusBarPipelineModule {
@@ -57,4 +61,15 @@
@Binds
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @WifiTableLog
+ fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("WifiTableLog", 100)
+ }
+ }
}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
similarity index 71%
copy from core/java/android/service/credentials/CreateCredentialResponse.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
index 73c9147..ac395a9 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
@@ -14,6 +14,12 @@
* limitations under the License.
*/
-package android.service.credentials;
+package com.android.systemui.statusbar.pipeline.dagger
-parcelable CreateCredentialResponse;
+import javax.inject.Qualifier
+
+/** Wifi logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index 062c3d1..8436b13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -17,12 +17,30 @@
package com.android.systemui.statusbar.pipeline.wifi.data.model
import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.log.table.Diffable
/** Provides information about the current wifi network. */
-sealed class WifiNetworkModel {
+sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
+
/** A model representing that we have no active wifi network. */
object Inactive : WifiNetworkModel() {
override fun toString() = "WifiNetwork.Inactive"
+
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is Inactive) {
+ return
+ }
+ row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
+
+ if (prevVal is CarrierMerged) {
+ // The only difference between CarrierMerged and Inactive is the type
+ return
+ }
+
+ // When changing from Active to Inactive, we need to log diffs to all the fields.
+ logDiffsFromActiveToNotActive(prevVal as Active, row)
+ }
}
/**
@@ -33,6 +51,21 @@
*/
object CarrierMerged : WifiNetworkModel() {
override fun toString() = "WifiNetwork.CarrierMerged"
+
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is CarrierMerged) {
+ return
+ }
+ row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
+
+ if (prevVal is Inactive) {
+ // The only difference between CarrierMerged and Inactive is the type.
+ return
+ }
+
+ // When changing from Active to CarrierMerged, we need to log diffs to all the fields.
+ logDiffsFromActiveToNotActive(prevVal as Active, row)
+ }
}
/** Provides information about an active wifi network. */
@@ -76,6 +109,41 @@
}
}
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal !is Active) {
+ row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
+ }
+
+ if (prevVal !is Active || prevVal.networkId != networkId) {
+ row.logChange(COL_NETWORK_ID, networkId)
+ }
+ if (prevVal !is Active || prevVal.isValidated != isValidated) {
+ row.logChange(COL_VALIDATED, isValidated)
+ }
+ if (prevVal !is Active || prevVal.level != level) {
+ row.logChange(COL_LEVEL, level ?: LEVEL_DEFAULT)
+ }
+ if (prevVal !is Active || prevVal.ssid != ssid) {
+ row.logChange(COL_SSID, ssid)
+ }
+
+ // TODO(b/238425913): The passpoint-related values are frequently never used, so it
+ // would be great to not log them when they're not used.
+ if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
+ }
+ if (prevVal !is Active ||
+ prevVal.isOnlineSignUpForPasspointAccessPoint !=
+ isOnlineSignUpForPasspointAccessPoint) {
+ row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
+ }
+ if (prevVal !is Active ||
+ prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
+ row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
+ }
+ }
+
+
override fun toString(): String {
// Only include the passpoint-related values in the string if we have them. (Most
// networks won't have them so they'll be mostly clutter.)
@@ -101,4 +169,37 @@
internal const val MAX_VALID_LEVEL = 4
}
}
+
+ internal fun logDiffsFromActiveToNotActive(prevActive: Active, row: TableRowLogger) {
+ row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+ row.logChange(COL_VALIDATED, false)
+ row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+ row.logChange(COL_SSID, null)
+
+ if (prevActive.isPasspointAccessPoint) {
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+ }
+ if (prevActive.isOnlineSignUpForPasspointAccessPoint) {
+ row.logChange(COL_ONLINE_SIGN_UP, false)
+ }
+ if (prevActive.passpointProviderFriendlyName != null) {
+ row.logChange(COL_PASSPOINT_NAME, null)
+ }
+ }
}
+
+const val TYPE_CARRIER_MERGED = "CarrierMerged"
+const val TYPE_INACTIVE = "Inactive"
+const val TYPE_ACTIVE = "Active"
+
+const val COL_NETWORK_TYPE = "type"
+const val COL_NETWORK_ID = "networkId"
+const val COL_VALIDATED = "isValidated"
+const val COL_LEVEL = "level"
+const val COL_SSID = "ssid"
+const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
+const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
+const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
+
+const val LEVEL_DEFAULT = -1
+const val NETWORK_ID_DEFAULT = -1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 93448c1d..a663536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -36,6 +36,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
@@ -82,6 +85,7 @@
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
+ @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
@@ -199,6 +203,12 @@
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "wifiNetwork",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
// There will be multiple wifi icons in different places that will frequently
// subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
// new subscribes will get the latest value immediately upon subscription. Otherwise, the
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
new file mode 100644
index 0000000..60a0258
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class UdfpsEnrollViewTest extends SysuiTestCase {
+
+ private static String ENROLL_PROGRESS_COLOR_LIGHT = "#699FF3";
+ private static String ENROLL_PROGRESS_COLOR_DARK = "#7DA7F1";
+
+ @Test
+ public void fingerprintUdfpsEnroll_usesCorrectThemeCheckmarkFillColor() {
+ final Configuration config = mContext.getResources().getConfiguration();
+ final boolean isDarkThemeOn = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ final int currentColor = mContext.getColor(R.color.udfps_enroll_progress);
+
+ assertThat(currentColor).isEqualTo(Color.parseColor(isDarkThemeOn
+ ? ENROLL_PROGRESS_COLOR_DARK : ENROLL_PROGRESS_COLOR_LIGHT));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 98ff8d1..c677f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -31,6 +31,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dump.DumpManager
@@ -110,6 +111,12 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
mContext.setMockPackageManager(packageManager)
+ mContext.orCreateTestableResources
+ .addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(componentName.packageName)
+ )
+
// Return true by default, we'll test the false path
`when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
@@ -482,6 +489,35 @@
}
@Test
+ fun testPackageNotPreferred_nullPanel() {
+ mContext.orCreateTestableResources
+ .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
+
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
fun testListingsNotModifiedByCallback() {
// This test checks that if the list passed to the callback is modified, it has no effect
// in the resulting services
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..cda7018
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.data.quickaffordance
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.utils.leaks.FakeFlashlightController
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
+
+ @Mock private lateinit var context: Context
+ private lateinit var flashlightController: FakeFlashlightController
+ private lateinit var underTest : FlashlightQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ injectLeakCheckedDependency(FlashlightController::class.java)
+ MockitoAnnotations.initMocks(this)
+
+ flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController
+ underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
+ }
+
+ @Test
+ fun `flashlight is off -- triggered -- icon is on and active`() = runTest {
+ //given
+ flashlightController.isEnabled = false
+ flashlightController.isAvailable = true
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ underTest.onTriggered(null)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(R.drawable.ic_flashlight_on,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = true
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ underTest.onTriggered(null)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(R.drawable.ic_flashlight_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightError()
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(R.drawable.ic_flashlight_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now off -- hidden`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightAvailabilityChanged(false)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest {
+ //given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightAvailabilityChanged(true)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active)
+ assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest {
+ //given
+ flashlightController.isEnabled = false
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)}
+
+ //when
+ flashlightController.onFlashlightAvailabilityChanged(true)
+ val lastValue = values.last()
+
+ //then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive)
+ assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight available -- picker state default`() = runTest {
+ //given
+ flashlightController.isAvailable = true
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+ }
+
+ @Test
+ fun `flashlight not available -- picker state unavailable`() = runTest {
+ //given
+ flashlightController.isAvailable = false
+
+ //when
+ val result = underTest.getPickerScreenState()
+
+ //then
+ assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
new file mode 100644
index 0000000..432764a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class TableChangeTest : SysuiTestCase() {
+
+ @Test
+ fun setString_isString() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set("fakeValue")
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("fakeValue")
+ }
+
+ @Test
+ fun setBoolean_isBoolean() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(true)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("true")
+ }
+
+ @Test
+ fun setInt_isInt() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(8900)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("8900")
+ }
+
+ @Test
+ fun setThenReset_isEmpty() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(8900)
+ underTest.reset(timestamp = 0, columnPrefix = "prefix", columnName = "name")
+
+ assertThat(underTest.hasData()).isFalse()
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
+ fun getName_hasPrefix() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+
+ assertThat(underTest.getName()).contains("fakePrefix")
+ assertThat(underTest.getName()).contains("fakeName")
+ }
+
+ @Test
+ fun getName_noPrefix() {
+ val underTest = TableChange(columnPrefix = "", columnName = "fakeName")
+
+ assertThat(underTest.getName()).contains("fakeName")
+ }
+
+ @Test
+ fun resetThenSet_hasNewValue() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "prefix", columnName = "original")
+ underTest.set("fakeValue")
+ underTest.reset(timestamp = 0, columnPrefix = "", columnName = "updated")
+ underTest.set(8900)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("updated")
+ assertThat(underTest.getName()).doesNotContain("prefix")
+ assertThat(underTest.getName()).doesNotContain("original")
+ assertThat(underTest.getVal()).isEqualTo("8900")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
new file mode 100644
index 0000000..688c66a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TableLogBufferTest : SysuiTestCase() {
+ private lateinit var underTest: TableLogBuffer
+
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var outputWriter: StringWriter
+
+ @Before
+ fun setup() {
+ systemClock = FakeSystemClock()
+ outputWriter = StringWriter()
+
+ underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun maxSizeZero_throwsException() {
+ TableLogBuffer(maxSize = 0, "name", systemClock)
+ }
+
+ @Test
+ fun dumpChanges_strChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("stringValChange", "prevStringVal")
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("stringValChange", "newStringVal")
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("prefix")
+ assertThat(dumpedString).contains("stringValChange")
+ assertThat(dumpedString).contains("newStringVal")
+ assertThat(dumpedString).doesNotContain("prevStringVal")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_boolChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", false)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", true)
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("prefix")
+ assertThat(dumpedString).contains("booleanValChange")
+ assertThat(dumpedString).contains("true")
+ assertThat(dumpedString).doesNotContain("false")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_intChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("intValChange", 12345)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("intValChange", 67890)
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("prefix")
+ assertThat(dumpedString).contains("intValChange")
+ assertThat(dumpedString).contains("67890")
+ assertThat(dumpedString).doesNotContain("12345")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_noPrefix() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", false)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", true)
+ }
+ }
+
+ // WHEN there's a blank prefix
+ underTest.logDiffs("", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ // THEN the dump still works
+ assertThat(dumpedString).contains("booleanValChange")
+ assertThat(dumpedString).contains("true")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
+ }
+
+ @Test
+ fun dumpChanges_multipleChangesForSameColumn_logs() {
+ lateinit var valToDump: String
+
+ val diffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("valChange", valToDump)
+ }
+ }
+
+ systemClock.setCurrentTimeMillis(12000L)
+ valToDump = "stateValue12"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(20000L)
+ valToDump = "stateValue20"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(40000L)
+ valToDump = "stateValue40"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(45000L)
+ valToDump = "stateValue45"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("valChange")
+ assertThat(dumpedString).contains("stateValue12")
+ assertThat(dumpedString).contains("stateValue20")
+ assertThat(dumpedString).contains("stateValue40")
+ assertThat(dumpedString).contains("stateValue45")
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(12000L))
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(20000L))
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(40000L))
+ assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(45000L))
+ }
+
+ @Test
+ fun dumpChanges_multipleChangesAtOnce_logs() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("status", "in progress")
+ row.logChange("connected", false)
+ }
+ }
+
+ underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("status")
+ assertThat(dumpedString).contains("in progress")
+ assertThat(dumpedString).contains("connected")
+ assertThat(dumpedString).contains("false")
+ }
+
+ @Test
+ fun dumpChanges_rotatesIfBufferIsFull() {
+ lateinit var valToDump: String
+
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("status", valToDump)
+ }
+ }
+
+ for (i in 0 until MAX_SIZE + 3) {
+ valToDump = "testString[$i]"
+ underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+ }
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).doesNotContain("testString[0]")
+ assertThat(dumpedString).doesNotContain("testString[1]")
+ assertThat(dumpedString).doesNotContain("testString[2]")
+ assertThat(dumpedString).contains("testString[3]")
+ assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
+ }
+
+ private fun dumpChanges(): String {
+ underTest.dumpChanges(PrintWriter(outputWriter))
+ return outputWriter.toString()
+ }
+
+ private abstract class TestDiffable : Diffable<TestDiffable> {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {}
+ }
+}
+
+private const val NAME = "TestTableBuffer"
+private const val MAX_SIZE = 10
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index e1007fa..858d0e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -35,6 +35,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -50,10 +52,12 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.VariableDateView
import com.android.systemui.statusbar.policy.VariableDateViewController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -104,7 +108,7 @@
@Mock
private lateinit var featureFlags: FeatureFlags
@Mock
- private lateinit var clock: TextView
+ private lateinit var clock: Clock
@Mock
private lateinit var date: VariableDateView
@Mock
@@ -138,6 +142,7 @@
private lateinit var qsConstraints: ConstraintSet
@Mock
private lateinit var largeScreenConstraints: ConstraintSet
+ @Mock private lateinit var demoModeController: DemoModeController
@JvmField @Rule
val mockitoRule = MockitoJUnit.rule()
@@ -146,10 +151,12 @@
private lateinit var controller: LargeScreenShadeHeaderController
private lateinit var carrierIconSlots: List<String>
private val configurationController = FakeConfigurationController()
+ private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode>
@Before
fun setUp() {
- whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ demoModeControllerCapture = argumentCaptor<DemoMode>()
+ whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
@@ -195,7 +202,8 @@
dumpManager,
featureFlags,
qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager
+ combinedShadeHeadersConstraintManager,
+ demoModeController
)
whenever(view.isAttachedToWindow).thenReturn(true)
controller.init()
@@ -617,6 +625,21 @@
}
@Test
+ fun demoMode_attachDemoMode() {
+ verify(demoModeController).addCallback(capture(demoModeControllerCapture))
+ demoModeControllerCapture.value.onDemoModeStarted()
+ verify(clock).onDemoModeStarted()
+ }
+
+ @Test
+ fun demoMode_detachDemoMode() {
+ controller.simulateViewDetached()
+ verify(demoModeController).removeCallback(capture(demoModeControllerCapture))
+ demoModeControllerCapture.value.onDemoModeFinished()
+ verify(clock).onDemoModeFinished()
+ }
+
+ @Test
fun animateOutOnStartCustomizing() {
val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
val duration = 1000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 90ae693..b4c8f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -13,6 +13,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -22,9 +24,12 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.VariableDateViewController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -52,7 +57,7 @@
@Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
@Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
@Mock private lateinit var featureFlags: FeatureFlags
- @Mock private lateinit var clock: TextView
+ @Mock private lateinit var clock: Clock
@Mock private lateinit var date: TextView
@Mock private lateinit var carrierGroup: QSCarrierGroup
@Mock private lateinit var batteryMeterView: BatteryMeterView
@@ -66,6 +71,7 @@
CombinedShadeHeadersConstraintManager
@Mock private lateinit var mockedContext: Context
+ @Mock private lateinit var demoModeController: DemoModeController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -76,7 +82,7 @@
@Before
fun setup() {
- whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
@@ -111,8 +117,9 @@
dumpManager,
featureFlags,
qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager
- )
+ combinedShadeHeadersConstraintManager,
+ demoModeController
+ )
whenever(view.isAttachedToWindow).thenReturn(true)
mLargeScreenShadeHeaderController.init()
carrierIconSlots = listOf(
@@ -230,4 +237,21 @@
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
verify(animator).start()
}
+
+ @Test
+ fun demoMode_attachDemoMode() {
+ val cb = argumentCaptor<DemoMode>()
+ verify(demoModeController).addCallback(capture(cb))
+ cb.value.onDemoModeStarted()
+ verify(clock).onDemoModeStarted()
+ }
+
+ @Test
+ fun demoMode_detachDemoMode() {
+ mLargeScreenShadeHeaderController.simulateViewDetached()
+ val cb = argumentCaptor<DemoMode>()
+ verify(demoModeController).removeCallback(capture(cb))
+ cb.value.onDemoModeFinished()
+ verify(clock).onDemoModeFinished()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
index 3d29d2b..30fd308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -18,8 +18,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
@SmallTest
@@ -48,6 +50,125 @@
WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
}
+ // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
+
+ @Test
+ fun logDiffs_inactiveToActive_logsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ }
+ @Test
+ fun logDiffs_activeToInactive_resetsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+ }
+
+ @Test
+ fun logDiffs_carrierMergedToActive_logsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ }
+ @Test
+ fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+ }
+
+ @Test
+ fun logDiffs_activeChangesLevel_onlyLevelLogged() {
+ val logger = TestLogger()
+ val prevActiveNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+ val newActiveNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 2,
+ ssid = "Test SSID"
+ )
+
+ newActiveNetwork.logDiffs(prevActiveNetwork, logger)
+
+ assertThat(logger.changes).isEqualTo(listOf(Pair(COL_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()))
+ }
+ }
+
companion object {
private const val NETWORK_ID = 2
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index a64a4bd..800f3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
@@ -69,6 +70,7 @@
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogger: TableLogBuffer
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
private lateinit var executor: Executor
@@ -804,6 +806,7 @@
broadcastDispatcher,
connectivityManager,
logger,
+ tableLogger,
executor,
scope,
wifiManagerToUse,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f6fd2cb..f68baf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -16,32 +16,71 @@
import android.testing.LeakCheck;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+import java.util.ArrayList;
+import java.util.List;
+
public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
implements FlashlightController {
+
+ private final List<FlashlightListener> callbacks = new ArrayList<>();
+
+ @VisibleForTesting
+ public boolean isAvailable;
+ @VisibleForTesting
+ public boolean isEnabled;
+ @VisibleForTesting
+ public boolean hasFlashlight;
+
public FakeFlashlightController(LeakCheck test) {
super(test, "flashlight");
}
+ @VisibleForTesting
+ public void onFlashlightAvailabilityChanged(boolean newValue) {
+ callbacks.forEach(
+ flashlightListener -> flashlightListener.onFlashlightAvailabilityChanged(newValue)
+ );
+ }
+
+ @VisibleForTesting
+ public void onFlashlightError() {
+ callbacks.forEach(FlashlightListener::onFlashlightError);
+ }
+
@Override
public boolean hasFlashlight() {
- return false;
+ return hasFlashlight;
}
@Override
public void setFlashlight(boolean newState) {
-
+ callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
}
@Override
public boolean isAvailable() {
- return false;
+ return isAvailable;
}
@Override
public boolean isEnabled() {
- return false;
+ return isEnabled;
+ }
+
+ @Override
+ public void addCallback(FlashlightListener listener) {
+ super.addCallback(listener);
+ callbacks.add(listener);
+ }
+
+ @Override
+ public void removeCallback(FlashlightListener listener) {
+ super.removeCallback(listener);
+ callbacks.remove(listener);
}
}
diff --git a/services/OWNERS b/services/OWNERS
index 495c0737..eace906 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -3,7 +3,7 @@
# art-team@ manages the system server profile
per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com
-per-file java/com/android/server/* = toddke@google.com,patb@google.com
+per-file java/com/android/server/* = patb@google.com #{LAST_RESORT_SUGGESTION}
per-file tests/servicestests/src/com/android/server/systemconfig/* = patb@google.com
per-file proguard.flags = jdduke@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 87d1668..630cd350 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -860,7 +860,8 @@
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
return IntPair.of(
- getClientStateLocked(userState),
+ combineUserStateAndProxyState(getClientStateLocked(userState),
+ mProxyManager.getStateLocked()),
client.mLastSentRelevantEventTypes);
} else {
userState.mUserClients.register(callback, client);
@@ -872,7 +873,9 @@
+ " and userId:" + mCurrentUserId);
}
return IntPair.of(
- (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+ (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
+ getClientStateLocked(userState), mProxyManager.getStateLocked())
+ : 0,
client.mLastSentRelevantEventTypes);
}
}
@@ -1003,6 +1006,7 @@
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
mUiAutomationManager.sendAccessibilityEventLocked(event);
+ mProxyManager.sendAccessibilityEvent(event);
}
private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
@@ -1143,9 +1147,9 @@
}
List<AccessibilityServiceConnection> services =
getUserStateLocked(resolvedUserId).mBoundServices;
- int numServices = services.size();
+ int numServices = services.size() + mProxyManager.getNumProxys();
interfacesToInterrupt = new ArrayList<>(numServices);
- for (int i = 0; i < numServices; i++) {
+ for (int i = 0; i < services.size(); i++) {
AccessibilityServiceConnection service = services.get(i);
IBinder a11yServiceBinder = service.mService;
IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
@@ -1153,6 +1157,7 @@
interfacesToInterrupt.add(a11yServiceInterface);
}
}
+ mProxyManager.addServiceInterfaces(interfacesToInterrupt);
}
for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
try {
@@ -1941,6 +1946,7 @@
mUiAutomationManager.getServiceInfo(), client)
? mUiAutomationManager.getRelevantEventTypes()
: 0;
+ relevantEventTypes |= mProxyManager.getRelevantEventTypes();
return relevantEventTypes;
}
@@ -2178,21 +2184,25 @@
updateAccessibilityEnabledSettingLocked(userState);
}
- void scheduleUpdateClientsIfNeeded(AccessibilityUserState userState) {
- synchronized (mLock) {
- scheduleUpdateClientsIfNeededLocked(userState);
- }
+ private int combineUserStateAndProxyState(int userState, int proxyState) {
+ return userState | proxyState;
}
void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
final int clientState = getClientStateLocked(userState);
- if (userState.getLastSentClientStateLocked() != clientState
+ final int proxyState = mProxyManager.getStateLocked();
+ if ((userState.getLastSentClientStateLocked() != clientState
+ || mProxyManager.getLastSentStateLocked() != proxyState)
&& (mGlobalClients.getRegisteredCallbackCount() > 0
- || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
+ || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
userState.setLastSentClientStateLocked(clientState);
+ mProxyManager.setLastStateLocked(proxyState);
+ // Send both the user and proxy state to the app for now.
+ // TODO(b/250929565): Send proxy state to proxy clients
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendStateToAllClients,
- this, clientState, userState.mUserId));
+ this, combineUserStateAndProxyState(clientState, proxyState),
+ userState.mUserId));
}
}
@@ -2434,7 +2444,8 @@
// binding we do an update pass after each bind event, so we run this
// code and register the callback if needed.
- boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked();
+ boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked()
+ || mProxyManager.canRetrieveInteractiveWindowsLocked();
List<AccessibilityServiceConnection> boundServices = userState.mBoundServices;
final int boundServiceCount = boundServices.size();
for (int i = 0; !observingWindows && (i < boundServiceCount); i++) {
@@ -3657,7 +3668,9 @@
+ "proxy-ed");
}
- mProxyManager.registerProxy(client, displayId);
+ mProxyManager.registerProxy(client, displayId, mContext,
+ sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
+ mWindowManagerService, mA11yWindowManager);
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index e5e1d02..ce269f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -696,7 +696,7 @@
final ResolveInfo resolveInfo = service.getServiceInfo().getResolveInfo();
if (resolveInfo == null) {
- // For InteractionBridge and UiAutomation
+ // For InteractionBridge, UiAutomation, and Proxy.
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 247f320..d7f9c12 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -16,8 +16,12 @@
package com.android.server.accessibility;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityTrace;
+import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.content.ComponentName;
@@ -31,6 +35,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
+import android.os.RemoteException;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityDisplayProxy;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -53,10 +58,6 @@
* TODO(241429275): Initialize this when a proxy is registered.
*/
public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
- // Names used to populate ComponentName and ResolveInfo
- private static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
- private static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
-
private int mDisplayId;
private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
@@ -76,6 +77,16 @@
}
/**
+ * Called when the proxy is registered.
+ */
+ void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
+ throws RemoteException {
+ mServiceInterface = serviceInterface;
+ mService = serviceInterface.asBinder();
+ mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+ }
+
+ /**
* Keeps mAccessibilityServiceInfo in sync with the proxy's list of AccessibilityServiceInfos.
*
* <p>This also sets the properties that are assumed to be populated by installed packages.
@@ -89,7 +100,7 @@
synchronized (mLock) {
mInstalledAndEnabledServices = infos;
final AccessibilityServiceInfo proxyInfo = mAccessibilityServiceInfo;
- // Reset values.
+ // Reset values. mAccessibilityServiceInfo is not completely reset since it is final
proxyInfo.flags = 0;
proxyInfo.eventTypes = 0;
proxyInfo.notificationTimeout = 0;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index a2ce610..2184878 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,9 +14,21 @@
* limitations under the License.
*/
package com.android.server.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
-import java.util.HashSet;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.List;
/**
* Manages proxy connections.
@@ -27,32 +39,185 @@
* TODO(241117292): Remove or cut down during simultaneous user refactoring.
*/
public class ProxyManager {
+ // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
+ // the infos of connection.setInstalledAndEnabledServices
+ static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
+ static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
+
private final Object mLock;
- private final HashSet<Integer> mDisplayIds = new HashSet<>();
+
+ // Used to determine if we should notify AccessibilityManager clients of updates.
+ // TODO(254545943): Separate this so each display id has its own state. Currently there is no
+ // way to identify from AccessibilityManager which proxy state should be returned.
+ private int mLastState = -1;
+
+ private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+ new SparseArray<>();
ProxyManager(Object lock) {
mLock = lock;
}
/**
- * TODO: Create the proxy service connection.
+ * Creates the service connection.
*/
- public void registerProxy(IAccessibilityServiceClient client, int displayId) {
- mDisplayIds.add(displayId);
+ public void registerProxy(IAccessibilityServiceClient client, int displayId,
+ Context context,
+ int id, Handler mainHandler,
+ AccessibilitySecurityPolicy securityPolicy,
+ AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
+ AccessibilityTrace trace,
+ WindowManagerInternal windowManagerInternal,
+ AccessibilityWindowManager awm) throws RemoteException {
+
+ // Set a default AccessibilityServiceInfo that is used before the proxy's info is
+ // populated. A proxy has the touch exploration and window capabilities.
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ | AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
+ final String componentClassDisplayName = PROXY_COMPONENT_CLASS_NAME + displayId;
+ info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME,
+ componentClassDisplayName));
+ ProxyAccessibilityServiceConnection connection =
+ new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
+ id, mainHandler, mLock, securityPolicy, systemSupport, trace,
+ windowManagerInternal,
+ awm, displayId);
+
+ mProxyA11yServiceConnections.put(displayId, connection);
+
+ // If the client dies, make sure to remove the connection.
+ IBinder.DeathRecipient deathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ client.asBinder().unlinkToDeath(this, 0);
+ clearConnection(displayId);
+ }
+ };
+ client.asBinder().linkToDeath(deathRecipient, 0);
+ // Notify apps that the service state has changed.
+ // A11yManager#A11yServicesStateChangeListener
+ connection.mSystemSupport.onClientChangeLocked(true);
+
+ connection.initializeServiceInterface(client);
}
/**
- * TODO: Unregister the proxy service connection based on display id.
+ * Unregister the proxy based on display id.
*/
public boolean unregisterProxy(int displayId) {
- mDisplayIds.remove(displayId);
- return true;
+ return clearConnection(displayId);
+ }
+
+ private boolean clearConnection(int displayId) {
+ if (mProxyA11yServiceConnections.contains(displayId)) {
+ mProxyA11yServiceConnections.remove(displayId);
+ return true;
+ }
+ return false;
}
/**
* Checks if a display id is being proxy-ed.
*/
public boolean isProxyed(int displayId) {
- return mDisplayIds.contains(displayId);
+ return mProxyA11yServiceConnections.contains(displayId);
}
-}
+
+ /**
+ * Sends AccessibilityEvents to all proxies.
+ * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
+ * TODO(b/250929565): Filtering should happen in the system, not in the proxy.
+ */
+ public void sendAccessibilityEvent(AccessibilityEvent event) {
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ proxy.notifyAccessibilityEvent(event);
+ }
+ }
+
+ /**
+ * Returns {@code true} if any proxy can retrieve windows.
+ * TODO(b/250929565): Retrieve per connection/user state.
+ */
+ public boolean canRetrieveInteractiveWindowsLocked() {
+ boolean observingWindows = false;
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy.mRetrieveInteractiveWindows) {
+ observingWindows = true;
+ break;
+ }
+ }
+ return observingWindows;
+ }
+
+ /**
+ * If there is at least one proxy, accessibility is enabled.
+ */
+ public int getStateLocked() {
+ int clientState = 0;
+ final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
+ if (a11yEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+ }
+ return clientState;
+ // TODO(b/254545943): When A11yManager is separated, include support for other properties
+ // like isTouchExplorationEnabled.
+ }
+
+ /**
+ * Gets the last state.
+ */
+ public int getLastSentStateLocked() {
+ return mLastState;
+ }
+
+ /**
+ * Sets the last state.
+ */
+ public void setLastStateLocked(int proxyState) {
+ mLastState = proxyState;
+ }
+
+ /**
+ * Returns the relevant event types of every proxy.
+ * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+ */
+ public int getRelevantEventTypes() {
+ int relevantEventTypes = 0;
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ relevantEventTypes |= proxy.getRelevantEventTypes();
+ }
+ return relevantEventTypes;
+ }
+
+ /**
+ * Gets the number of current proxy connections.
+ * @return
+ */
+ public int getNumProxys() {
+ return mProxyA11yServiceConnections.size();
+ }
+
+ /**
+ * Adds the service interfaces to a list.
+ * @param interfaces
+ */
+ public void addServiceInterfaces(List<IAccessibilityServiceClient> interfaces) {
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ final IBinder proxyBinder = proxy.mService;
+ final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+ if ((proxyBinder != null) && (proxyInterface != null)) {
+ interfaces.add(proxyInterface);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 3cfae60..8baae53a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -35,6 +35,7 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
+import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.KeyguardManager;
@@ -257,6 +258,9 @@
private boolean mIsProviderInfoPersisted;
private boolean mIsCombinedBroadcastEnabled;
+ // Mark widget lifecycle broadcasts as 'interactive'
+ private Bundle mInteractiveBroadcast;
+
AppWidgetServiceImpl(Context context) {
mContext = context;
}
@@ -286,6 +290,11 @@
Slog.d(TAG, "App widget provider info will not be persisted on this device");
}
+ BroadcastOptions opts = BroadcastOptions.makeBasic();
+ opts.setBackgroundActivityStartsAllowed(false);
+ opts.setInteractive(true);
+ mInteractiveBroadcast = opts.toBundle();
+
computeMaximumWidgetBitmapMemory();
registerBroadcastReceiver();
registerOnCrossProfileProvidersChangedListener();
@@ -2379,33 +2388,40 @@
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(p.id.componentName);
- sendBroadcastAsUser(intent, p.id.getProfile());
+ // Placing a widget is something users expect to be UX-responsive, so mark this
+ // broadcast as interactive
+ sendBroadcastAsUser(intent, p.id.getProfile(), true);
}
private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.id.componentName);
- sendBroadcastAsUser(intent, p.id.getProfile());
+ // Enabling the widget is something users expect to be UX-responsive, so mark this
+ // broadcast as interactive
+ sendBroadcastAsUser(intent, p.id.getProfile(), true);
}
private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(provider.id.componentName);
- sendBroadcastAsUser(intent, provider.id.getProfile());
+ // Periodic background widget update heartbeats are not an interactive use case
+ sendBroadcastAsUser(intent, provider.id.getProfile(), false);
}
private void sendDeletedIntentLocked(Widget widget) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
intent.setComponent(widget.provider.id.componentName);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
- sendBroadcastAsUser(intent, widget.provider.id.getProfile());
+ // Cleanup after deletion isn't an interactive UX case
+ sendBroadcastAsUser(intent, widget.provider.id.getProfile(), false);
}
private void sendDisabledIntentLocked(Provider provider) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
intent.setComponent(provider.id.componentName);
- sendBroadcastAsUser(intent, provider.id.getProfile());
+ // Cleanup after disable isn't an interactive UX case
+ sendBroadcastAsUser(intent, provider.id.getProfile(), false);
}
public void sendOptionsChangedIntentLocked(Widget widget) {
@@ -2413,7 +2429,9 @@
intent.setComponent(widget.provider.id.componentName);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, widget.options);
- sendBroadcastAsUser(intent, widget.provider.id.getProfile());
+ // The user's changed the options, so seeing them take effect promptly is
+ // an interactive UX expectation
+ sendBroadcastAsUser(intent, widget.provider.id.getProfile(), true);
}
@GuardedBy("mLock")
@@ -3666,10 +3684,17 @@
return null;
}
- private void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
+ /**
+ * Sends a widget lifecycle broadcast within the specified user. If {@code isInteractive}
+ * is specified as {@code true}, the broadcast dispatch mechanism will be told that it
+ * is related to a UX flow with user-visible expectations about timely dispatch. This
+ * should only be used for broadcast flows that do have such expectations.
+ */
+ private void sendBroadcastAsUser(Intent intent, UserHandle userHandle, boolean isInteractive) {
final long identity = Binder.clearCallingIdentity();
try {
- mContext.sendBroadcastAsUser(intent, userHandle);
+ mContext.sendBroadcastAsUser(intent, userHandle, null,
+ isInteractive ? mInteractiveBroadcast : null);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5008,18 +5033,20 @@
private void sendWidgetRestoreBroadcastLocked(String action, Provider provider,
Host host, int[] oldIds, int[] newIds, UserHandle userHandle) {
+ // Users expect restore to emplace widgets properly ASAP, so flag these as
+ // being interactive broadcast dispatches
Intent intent = new Intent(action);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
if (provider != null) {
intent.setComponent(provider.id.componentName);
- sendBroadcastAsUser(intent, userHandle);
+ sendBroadcastAsUser(intent, userHandle, true);
}
if (host != null) {
intent.setComponent(null);
intent.setPackage(host.id.packageName);
intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.id.hostId);
- sendBroadcastAsUser(intent, userHandle);
+ sendBroadcastAsUser(intent, userHandle, true);
}
}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 0fe90b1..f5d6836 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -9,7 +9,7 @@
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
-import android.app.backup.BackupManager;
+import android.app.backup.BackupAnnotations;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.IBackupCallback;
@@ -148,7 +148,7 @@
try {
return mBackupManagerService.bindToAgentSynchronous(targetApp,
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
- BackupManager.OperationType.BACKUP);
+ BackupAnnotations.BackupDestination.CLOUD);
} catch (SecurityException e) {
Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
+ ". " + e);
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 4cf63b3..ce3e628 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -46,8 +46,8 @@
import android.app.IBackupAgent;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManager;
@@ -405,7 +405,7 @@
private long mAncestralToken = 0;
private long mCurrentToken = 0;
@Nullable private File mAncestralSerialNumberFile;
- @OperationType private volatile long mAncestralOperationType;
+ @BackupDestination private volatile long mAncestralBackupDestination;
private final ContentObserver mSetupObserver;
private final BroadcastReceiver mRunInitReceiver;
@@ -550,7 +550,7 @@
mActivityManager = ActivityManager.getService();
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mScheduledBackupEligibility = getEligibilityRules(mPackageManager, userId,
- OperationType.BACKUP);
+ BackupDestination.CLOUD);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -844,8 +844,8 @@
mAncestralToken = ancestralToken;
}
- public void setAncestralOperationType(@OperationType int operationType) {
- mAncestralOperationType = operationType;
+ public void setAncestralBackupDestination(@BackupDestination int backupDestination) {
+ mAncestralBackupDestination = backupDestination;
}
public long getCurrentToken() {
@@ -1619,14 +1619,14 @@
/** Fires off a backup agent, blocking until it attaches or times out. */
@Nullable
public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
- @OperationType int operationType) {
+ @BackupDestination int backupDestination) {
IBackupAgent agent = null;
synchronized (mAgentConnectLock) {
mConnecting = true;
mConnectedAgent = null;
try {
if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
- operationType)) {
+ backupDestination)) {
Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
// success; wait for the agent to arrive
@@ -1776,8 +1776,9 @@
}
private BackupEligibilityRules getEligibilityRulesForRestoreAtInstall(long restoreToken) {
- if (mAncestralOperationType == OperationType.MIGRATION && restoreToken == mAncestralToken) {
- return getEligibilityRulesForOperation(OperationType.MIGRATION);
+ if (mAncestralBackupDestination == BackupDestination.DEVICE_TRANSFER
+ && restoreToken == mAncestralToken) {
+ return getEligibilityRulesForOperation(BackupDestination.DEVICE_TRANSFER);
} else {
// If we're not using the ancestral data set, it means we're restoring from a backup
// that happened on this device.
@@ -1856,14 +1857,14 @@
final TransportConnection transportConnection;
final String transportDirName;
- int operationType;
+ int backupDestination;
try {
transportDirName =
mTransportManager.getTransportDirName(
mTransportManager.getCurrentTransportName());
transportConnection =
mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()");
- operationType = getOperationTypeFromTransport(transportConnection);
+ backupDestination = getBackupDestinationFromTransport(transportConnection);
} catch (TransportNotRegisteredException | TransportNotAvailableException
| RemoteException e) {
BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
@@ -1876,7 +1877,7 @@
OnTaskFinishedListener listener =
caller -> mTransportManager.disposeOfTransportClient(transportConnection, caller);
BackupEligibilityRules backupEligibilityRules = getEligibilityRulesForOperation(
- operationType);
+ backupDestination);
Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
msg.obj = getRequestBackupParams(packages, observer, monitor, flags, backupEligibilityRules,
@@ -2373,7 +2374,7 @@
/* monitor */ null,
/* userInitiated */ false,
"BMS.beginFullBackup()",
- getEligibilityRulesForOperation(OperationType.BACKUP));
+ getEligibilityRulesForOperation(BackupDestination.CLOUD));
} catch (IllegalStateException e) {
Slog.w(TAG, "Failed to start backup", e);
runBackup = false;
@@ -2835,7 +2836,7 @@
Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup..."));
BackupEligibilityRules eligibilityRules = getEligibilityRulesForOperation(
- OperationType.ADB_BACKUP);
+ BackupDestination.ADB_BACKUP);
AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
pkgList, eligibilityRules);
@@ -2924,7 +2925,7 @@
/* monitor */ null,
/* userInitiated */ false,
"BMS.fullTransportBackup()",
- getEligibilityRulesForOperation(OperationType.BACKUP));
+ getEligibilityRulesForOperation(BackupDestination.CLOUD));
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
mWakelock.acquire();
(new Thread(task, "full-transport-master")).start();
@@ -3917,12 +3918,12 @@
}
}
- int operationType;
+ int backupDestination;
TransportConnection transportConnection = null;
try {
transportConnection = mTransportManager.getTransportClientOrThrow(
transport, /* caller */"BMS.beginRestoreSession");
- operationType = getOperationTypeFromTransport(transportConnection);
+ backupDestination = getBackupDestinationFromTransport(transportConnection);
} catch (TransportNotAvailableException | TransportNotRegisteredException
| RemoteException e) {
Slog.w(TAG, "Failed to get operation type from transport: " + e);
@@ -3951,7 +3952,7 @@
return null;
}
mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport,
- getEligibilityRulesForOperation(operationType));
+ getEligibilityRulesForOperation(backupDestination));
mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
mAgentTimeoutParameters.getRestoreSessionTimeoutMillis());
}
@@ -4037,14 +4038,14 @@
}
public BackupEligibilityRules getEligibilityRulesForOperation(
- @OperationType int operationType) {
- return getEligibilityRules(mPackageManager, mUserId, operationType);
+ @BackupDestination int backupDestination) {
+ return getEligibilityRules(mPackageManager, mUserId, backupDestination);
}
private static BackupEligibilityRules getEligibilityRules(PackageManager packageManager,
- int userId, @OperationType int operationType) {
+ int userId, @BackupDestination int backupDestination) {
return new BackupEligibilityRules(packageManager,
- LocalServices.getService(PackageManagerInternal.class), userId, operationType);
+ LocalServices.getService(PackageManagerInternal.class), userId, backupDestination);
}
/** Prints service state for 'dumpsys backup'. */
@@ -4200,21 +4201,22 @@
}
@VisibleForTesting
- @OperationType int getOperationTypeFromTransport(TransportConnection transportConnection)
+ @BackupDestination int getBackupDestinationFromTransport(
+ TransportConnection transportConnection)
throws TransportNotAvailableException, RemoteException {
if (!shouldUseNewBackupEligibilityRules()) {
// Return the default to stick to the legacy behaviour.
- return OperationType.BACKUP;
+ return BackupDestination.CLOUD;
}
final long oldCallingId = Binder.clearCallingIdentity();
try {
BackupTransportClient transport = transportConnection.connectOrThrow(
- /* caller */ "BMS.getOperationTypeFromTransport");
+ /* caller */ "BMS.getBackupDestinationFromTransport");
if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) {
- return OperationType.MIGRATION;
+ return BackupDestination.DEVICE_TRANSFER;
} else {
- return OperationType.BACKUP;
+ return BackupDestination.CLOUD;
}
} finally {
Binder.restoreCallingIdentity(oldCallingId);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 379ae52..65682f4 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -315,7 +315,7 @@
mAgent =
backupManagerService.bindToAgentSynchronous(
mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL,
- mBackupEligibilityRules.getOperationType());
+ mBackupEligibilityRules.getBackupDestination());
}
return mAgent != null;
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 95cc289..3ff6ba7 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -20,7 +20,7 @@
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreSet;
import android.os.Handler;
@@ -240,7 +240,7 @@
/* userInitiated */ false,
/* nonIncremental */ false,
backupManagerService.getEligibilityRulesForOperation(
- OperationType.BACKUP));
+ BackupDestination.CLOUD));
} catch (Exception e) {
// unable to ask the transport its dir name -- transient failure, since
// the above check succeeded. Try again next time.
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index fd9c834..ca92b69 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -741,7 +741,7 @@
agent =
mBackupManagerService.bindToAgentSynchronous(
packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL,
- mBackupEligibilityRules.getOperationType());
+ mBackupEligibilityRules.getBackupDestination());
if (agent == null) {
mReporter.onAgentError(packageName);
throw AgentException.transitory();
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 8b1d561..d3e4f13 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -16,8 +16,6 @@
package com.android.server.backup.restore;
-import static android.app.backup.BackupManager.OperationType;
-
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
@@ -26,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
@@ -299,9 +298,9 @@
private BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) {
// TODO(b/182986784): Remove device name comparison once a designated field for operation
// type is added to RestoreSet object.
- int operationType = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
- ? OperationType.MIGRATION : OperationType.BACKUP;
- return mBackupManagerService.getEligibilityRulesForOperation(operationType);
+ int backupDestination = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
+ ? BackupDestination.DEVICE_TRANSFER : BackupDestination.CLOUD;
+ return mBackupManagerService.getEligibilityRulesForOperation(backupDestination);
}
public synchronized int restorePackage(String packageName, IRestoreObserver observer,
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index e78c8d1..b042c30 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -28,6 +28,7 @@
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupManager;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManagerMonitor;
@@ -398,7 +399,7 @@
FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
: ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
- mBackupEligibilityRules.getOperationType());
+ mBackupEligibilityRules.getBackupDestination());
mAgentPackage = pkg;
} catch (IOException | NameNotFoundException e) {
// fall through to error handling
@@ -707,7 +708,8 @@
}
private boolean isRestorableFile(FileMetadata info) {
- if (mBackupEligibilityRules.getOperationType() == BackupManager.OperationType.MIGRATION) {
+ if (mBackupEligibilityRules.getBackupDestination()
+ == BackupAnnotations.BackupDestination.DEVICE_TRANSFER) {
// Everything is eligible for device-to-device migration.
return true;
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 22af19e..515a172 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -24,7 +24,7 @@
import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_VERSION;
-import android.app.backup.BackupManager;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.IFullBackupRestoreObserver;
import android.content.pm.PackageManagerInternal;
import android.os.ParcelFileDescriptor;
@@ -112,7 +112,7 @@
BackupEligibilityRules eligibilityRules = new BackupEligibilityRules(
mBackupManagerService.getPackageManager(),
LocalServices.getService(PackageManagerInternal.class),
- mBackupManagerService.getUserId(), BackupManager.OperationType.ADB_BACKUP);
+ mBackupManagerService.getUserId(), BackupDestination.ADB_BACKUP);
FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService,
mOperationStorage, null, mObserver, null, null,
true, 0 /*unused*/, true, eligibilityRules);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 9f89339..18e28de 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -675,7 +675,7 @@
mAgent = backupManagerService.bindToAgentSynchronous(
mCurrentPackage.applicationInfo,
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
- mBackupEligibilityRules.getOperationType());
+ mBackupEligibilityRules.getBackupDestination());
if (mAgent == null) {
Slog.w(TAG, "Can't find backup agent for " + packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
@@ -1160,8 +1160,8 @@
if (mIsSystemRestore && mPmAgent != null) {
backupManagerService.setAncestralPackages(mPmAgent.getRestoredPackages());
backupManagerService.setAncestralToken(mToken);
- backupManagerService.setAncestralOperationType(
- mBackupEligibilityRules.getOperationType());
+ backupManagerService.setAncestralBackupDestination(
+ mBackupEligibilityRules.getBackupDestination());
backupManagerService.writeRestoreTokens();
}
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index d0300ff..7f0b56f 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -23,7 +23,7 @@
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.Nullable;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupTransport;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -61,7 +61,7 @@
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final int mUserId;
- @OperationType private final int mOperationType;
+ @BackupDestination private final int mBackupDestination;
/**
* When this change is enabled, {@code adb backup} is automatically turned on for apps
@@ -85,17 +85,17 @@
PackageManagerInternal packageManagerInternal,
int userId) {
return new BackupEligibilityRules(packageManager, packageManagerInternal, userId,
- OperationType.BACKUP);
+ BackupDestination.CLOUD);
}
public BackupEligibilityRules(PackageManager packageManager,
PackageManagerInternal packageManagerInternal,
int userId,
- @OperationType int operationType) {
+ @BackupDestination int backupDestination) {
mPackageManager = packageManager;
mPackageManagerInternal = packageManagerInternal;
mUserId = userId;
- mOperationType = operationType;
+ mBackupDestination = backupDestination;
}
/**
@@ -111,7 +111,7 @@
* </ol>
*
* However, the above eligibility rules are ignored for non-system apps in in case of
- * device-to-device migration, see {@link OperationType}.
+ * device-to-device migration, see {@link BackupDestination}.
*/
@VisibleForTesting
public boolean appIsEligibleForBackup(ApplicationInfo app) {
@@ -152,22 +152,22 @@
/**
* Check if this app allows backup. Apps can opt out of backup by stating
* android:allowBackup="false" in their manifest. However, this flag is ignored for non-system
- * apps during device-to-device migrations, see {@link OperationType}.
+ * apps during device-to-device migrations, see {@link BackupDestination}.
*
* @param app The app under check.
* @return boolean indicating whether backup is allowed.
*/
public boolean isAppBackupAllowed(ApplicationInfo app) {
boolean allowBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
- switch (mOperationType) {
- case OperationType.MIGRATION:
+ switch (mBackupDestination) {
+ case BackupDestination.DEVICE_TRANSFER:
// Backup / restore of all non-system apps is force allowed during
// device-to-device migration.
boolean isSystemApp = (app.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
boolean ignoreAllowBackup = !isSystemApp && CompatChanges.isChangeEnabled(
IGNORE_ALLOW_BACKUP_IN_D2D, app.packageName, UserHandle.of(mUserId));
return ignoreAllowBackup || allowBackup;
- case OperationType.ADB_BACKUP:
+ case BackupDestination.ADB_BACKUP:
String packageName = app.packageName;
if (packageName == null) {
Slog.w(TAG, "Invalid ApplicationInfo object");
@@ -207,10 +207,10 @@
// All other apps can use adb backup only when running in debuggable mode.
return isDebuggable;
}
- case OperationType.BACKUP:
+ case BackupDestination.CLOUD:
return allowBackup;
default:
- Slog.w(TAG, "Unknown operation type:" + mOperationType);
+ Slog.w(TAG, "Unknown operation type:" + mBackupDestination);
return false;
}
}
@@ -398,7 +398,7 @@
}
}
- public int getOperationType() {
- return mOperationType;
+ public int getBackupDestination() {
+ return mBackupDestination;
}
}
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 02c6ca2..27ee627 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -91,7 +91,7 @@
private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
private static final int DEFAULT_QUOTA_KB = 10 * 1024;
private static final int DEFAULT_QUOTA_PERCENT = 10;
- private static final int DEFAULT_RESERVE_PERCENT = 10;
+ private static final int DEFAULT_RESERVE_PERCENT = 0;
private static final int QUOTA_RESCAN_MILLIS = 5000;
private static final boolean PROFILE_DUMP = false;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 30d4b8b..ca86021c 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -52,8 +52,8 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
import android.telephony.BarringInfo;
+import android.telephony.CallAttributes;
import android.telephony.CallQuality;
-import android.telephony.CallState;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
@@ -82,7 +82,6 @@
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.telephony.emergency.EmergencyNumber;
-import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -350,9 +349,9 @@
private CallQuality[] mCallQuality;
- private ArrayList<List<CallState>> mCallStateLists;
+ private CallAttributes[] mCallAttributes;
- // network type of the call associated with the mCallStateLists and mCallQuality
+ // network type of the call associated with the mCallAttributes and mCallQuality
private int[] mCallNetworkType;
private int[] mSrvccState;
@@ -688,6 +687,7 @@
mCallPreciseDisconnectCause = copyOf(mCallPreciseDisconnectCause, mNumPhones);
mCallQuality = copyOf(mCallQuality, mNumPhones);
mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
+ mCallAttributes = copyOf(mCallAttributes, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
@@ -707,7 +707,6 @@
cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
cutListToSize(mCarrierPrivilegeStates, mNumPhones);
cutListToSize(mCarrierServiceStates, mNumPhones);
- cutListToSize(mCallStateLists, mNumPhones);
return;
}
@@ -731,7 +730,8 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallStateLists.add(i, new ArrayList<>());
+ mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -799,7 +799,7 @@
mCallPreciseDisconnectCause = new int[numPhones];
mCallQuality = new CallQuality[numPhones];
mCallNetworkType = new int[numPhones];
- mCallStateLists = new ArrayList<>();
+ mCallAttributes = new CallAttributes[numPhones];
mPreciseDataConnectionStates = new ArrayList<>();
mCellInfo = new ArrayList<>(numPhones);
mImsReasonInfo = new ArrayList<>();
@@ -837,7 +837,8 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallStateLists.add(i, new ArrayList<>());
+ mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -1335,7 +1336,7 @@
}
if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
+ r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2170,30 +2171,11 @@
}
}
- /**
- * Send a notification to registrants that the precise call state has changed.
- *
- * @param phoneId the phoneId carrying the data connection
- * @param subId the subscriptionId for the data connection
- * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
- * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId()} for
- * ringing, foreground & background calls.
- * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
- * background calls.
- * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
- */
- public void notifyPreciseCallState(int phoneId, int subId,
- @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
- @Annotation.ImsCallServiceType int[] imsServiceTypes,
- @Annotation.ImsCallType int[] imsCallTypes) {
+ public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
+ int foregroundCallState, int backgroundCallState) {
if (!checkNotifyPermission("notifyPreciseCallState()")) {
return;
}
-
- int ringingCallState = callStates[CallState.CALL_CLASSIFICATION_RINGING];
- int foregroundCallState = callStates[CallState.CALL_CLASSIFICATION_FOREGROUND];
- int backgroundCallState = callStates[CallState.CALL_CLASSIFICATION_BACKGROUND];
-
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mRingingCallState[phoneId] = ringingCallState;
@@ -2204,11 +2186,11 @@
backgroundCallState,
DisconnectCause.NOT_VALID,
PreciseDisconnectCause.NOT_VALID);
- boolean notifyCallState = true;
+ boolean notifyCallAttributes = true;
if (mCallQuality == null) {
log("notifyPreciseCallState: mCallQuality is null, "
+ "skipping call attributes");
- notifyCallState = false;
+ notifyCallAttributes = false;
} else {
// If the precise call state is no longer active, reset the call network type
// and call quality.
@@ -2217,54 +2199,8 @@
mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mCallQuality[phoneId] = createCallQuality();
}
- mCallStateLists.get(phoneId).clear();
- if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
- CallQuality callQuality = mCallQuality[phoneId];
- mCallStateLists.get(phoneId).add(
- new CallState.Builder(
- callStates[CallState.CALL_CLASSIFICATION_FOREGROUND])
- .setNetworkType(mCallNetworkType[phoneId])
- .setCallQuality(callQuality)
- .setCallClassification(
- CallState.CALL_CLASSIFICATION_FOREGROUND)
- .setImsCallSessionId(imsCallIds[
- CallState.CALL_CLASSIFICATION_FOREGROUND])
- .setImsCallServiceType(imsServiceTypes[
- CallState.CALL_CLASSIFICATION_FOREGROUND])
- .setImsCallType(imsCallTypes[
- CallState.CALL_CLASSIFICATION_FOREGROUND]).build());
-
- }
- if (backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
- mCallStateLists.get(phoneId).add(
- new CallState.Builder(
- callStates[CallState.CALL_CLASSIFICATION_BACKGROUND])
- .setNetworkType(mCallNetworkType[phoneId])
- .setCallQuality(createCallQuality())
- .setCallClassification(
- CallState.CALL_CLASSIFICATION_BACKGROUND)
- .setImsCallSessionId(imsCallIds[
- CallState.CALL_CLASSIFICATION_BACKGROUND])
- .setImsCallServiceType(imsServiceTypes[
- CallState.CALL_CLASSIFICATION_BACKGROUND])
- .setImsCallType(imsCallTypes[
- CallState.CALL_CLASSIFICATION_BACKGROUND]).build());
- }
- if (ringingCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
- mCallStateLists.get(phoneId).add(
- new CallState.Builder(
- callStates[CallState.CALL_CLASSIFICATION_RINGING])
- .setNetworkType(mCallNetworkType[phoneId])
- .setCallQuality(createCallQuality())
- .setCallClassification(
- CallState.CALL_CLASSIFICATION_RINGING)
- .setImsCallSessionId(imsCallIds[
- CallState.CALL_CLASSIFICATION_RINGING])
- .setImsCallServiceType(imsServiceTypes[
- CallState.CALL_CLASSIFICATION_RINGING])
- .setImsCallType(imsCallTypes[
- CallState.CALL_CLASSIFICATION_RINGING]).build());
- }
+ mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
+ mCallNetworkType[phoneId], mCallQuality[phoneId]);
}
for (Record r : mRecords) {
@@ -2277,11 +2213,11 @@
mRemoveList.add(r.binder);
}
}
- if (notifyCallState && r.matchTelephonyCallbackEvent(
+ if (notifyCallAttributes && r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2579,29 +2515,15 @@
// merge CallQuality with PreciseCallState and network type
mCallQuality[phoneId] = callQuality;
mCallNetworkType[phoneId] = callNetworkType;
- if (mCallStateLists.get(phoneId).size() > 0
- && mCallStateLists.get(phoneId).get(0).getCallState()
- == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
- CallState prev = mCallStateLists.get(phoneId).remove(0);
- mCallStateLists.get(phoneId).add(
- 0, new CallState.Builder(prev.getCallState())
- .setNetworkType(callNetworkType)
- .setCallQuality(callQuality)
- .setCallClassification(prev.getCallClassification())
- .setImsCallSessionId(prev.getImsCallSessionId())
- .setImsCallServiceType(prev.getImsCallServiceType())
- .setImsCallType(prev.getImsCallType()).build());
- } else {
- log("There is no active call to report CallQaulity");
- return;
- }
+ mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
+ callNetworkType, callQuality);
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -3069,6 +2991,7 @@
pw.println("mSrvccState=" + mSrvccState[i]);
pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
pw.println("mCallQuality=" + mCallQuality[i]);
+ pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dd3020a..4539e9e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -33,6 +33,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
@@ -227,7 +228,8 @@
private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
private static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
- private static final boolean DEBUG_SHORT_SERVICE = DEBUG_SERVICE;
+ // STOPSHIP(b/260012573) turn it off.
+ private static final boolean DEBUG_SHORT_SERVICE = true; // DEBUG_SERVICE;
private static final boolean LOG_SERVICE_START_STOP = DEBUG_SERVICE;
@@ -1286,6 +1288,8 @@
return;
}
+ maybeStopShortFgsTimeoutLocked(service);
+
final int uid = service.appInfo.uid;
final String packageName = service.name.getPackageName();
final String serviceName = service.name.getClassName();
@@ -1469,6 +1473,8 @@
}
}
+ maybeStopShortFgsTimeoutLocked(r);
+
final int uid = r.appInfo.uid;
final String packageName = r.name.getPackageName();
final String serviceName = r.name.getClassName();
@@ -1836,6 +1842,14 @@
+ String.format("0x%08X", manifestType)
+ " in service element of manifest file");
}
+ if ((foregroundServiceType & FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) != 0
+ && foregroundServiceType != FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) {
+ Slog.w(TAG_SERVICE, "startForeground(): FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+ + " is combined with other types. SHORT_SERVICE will be ignored.");
+ // In this case, the service will be handled as a non-short, regular FGS
+ // anyway, so we just remove the SHORT_SERVICE type.
+ foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ }
}
boolean alreadyStartedOp = false;
@@ -1886,14 +1900,51 @@
int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
if (!ignoreForeground) {
- // TODO(short-service): There's a known long-standing bug that allows
- // a abound service to become "foreground" if setForeground() is called
- // (without actually "starting" it).
- // Unfortunately we can't just "fix" it because some apps are relying on it,
- // but this will cause a problem to short-fgs, so we should disallow it if
- // this happens and the type is SHORT_SERVICE.
- //
- // OTOH, if a valid short-service (which has to be "started"), happens to
+ if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
+ && !r.startRequested) {
+ // There's a long standing bug that allows a bound service to become
+ // a foreground service *even when it's not started*.
+ // Unfortunately, there are apps relying on this behavior, so we can't just
+ // suddenly disallow it.
+ // However, this would be very problematic if used with a short-FGS, so we
+ // explicitly disallow this combination.
+ // TODO(short-service): Change to another exception type?
+ throw new IllegalStateException(
+ "startForeground(SHORT_SERVICE) called on a service that's not"
+ + " started.");
+ }
+ // If the service is already an FGS, and the type is changing, then we
+ // may need to do some extra work here.
+ if (r.isForeground && (r.foregroundServiceType != foregroundServiceType)) {
+ // TODO(short-service): Consider transitions:
+ // A. Short -> other types:
+ // Apply the BG restriction again. Don't just allow it.
+ // i.e. unless the app is in a situation where it's allowed to start
+ // a FGS, this transition shouldn't be allowed.
+ // ... But think about it more, there may be a case this should be
+ // allowed.
+ //
+ // If the transition is allowed, stop the timeout.
+ // If the transition is _not_ allowed... keep the timeout?
+ //
+ // B. Short -> Short:
+ // Allowed, but the timeout won't reset. The original timeout is used.
+ // C. Other -> short:
+ // This should always be allowed.
+ // A timeout should start.
+
+ // For now, let's just disallow transition from / to SHORT_SERVICE.
+ final boolean isNewTypeShortFgs =
+ foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ if (r.isShortFgs() != isNewTypeShortFgs) {
+ // TODO(short-service): We should (probably) allow it.
+ throw new IllegalArgumentException(
+ "setForeground(): Changing foreground service type from / to "
+ + " SHORT_SERVICE is now allowed");
+ }
+ }
+
+ // If a valid short-service (which has to be "started"), happens to
// also be bound, then we still _will_ apply a timeout, because it still has
// to be stopped.
if (r.mStartForegroundCount == 0) {
@@ -1932,24 +1983,6 @@
// on the same sarvice after it's created, regardless of whether
// stopForeground() has been called or not.
- // TODO(short-service): Consider transitions:
- // A. Short -> other types:
- // Apply the BG restriction again. Don't just allow it.
- // i.e. unless the app is in a situation where it's allowed to start
- // a FGS, this transition shouldn't be allowed.
- // ... But think about it more, there may be a case this should be
- // allowed.
- //
- // If the transition is allowed, stop the timeout.
- // If the transition is _not_ allowed... keep the timeout?
- //
- // B. Short -> Short:
- // This should be the same as case A
- // If this is allowed, the new timeout should start.
- // C. Other -> short:
- // This should always be allowed.
- // A timeout should start.
-
// The second or later time startForeground() is called after service is
// started. Check for app state again.
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
@@ -2106,8 +2139,11 @@
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- // TODO(short-service): Start counting a timeout.
-
+ // Note, we'll get here if setForeground(SHORT_SERVICE) is called on a
+ // already short-fgs.
+ // In that case, because ShortFgsInfo is already set, this method
+ // will be noop.
+ maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2141,7 +2177,7 @@
decActiveForegroundAppLocked(smap, r);
}
- // TODO(short-service): Stop the timeout. (any better place to do it?)
+ maybeStopShortFgsTimeoutLocked(r);
// Adjust notification handling before setting isForeground to false, because
// that state is relevant to the notification policy side.
@@ -2886,6 +2922,90 @@
psr.setHasReportedForegroundServices(anyForeground);
}
+ void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
+ }
+
+ /**
+ * If {@code sr} is of a short-fgs, start a short-FGS timeout.
+ */
+ private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr) {
+ if (!sr.isShortFgs()) {
+ return;
+ }
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
+ }
+ if (sr.hasShortFgsInfo()) {
+ sr.getShortFgsInfo().update();
+ } else {
+ sr.setShortFgsInfo(SystemClock.uptimeMillis());
+ }
+ unscheduleShortFgsTimeoutLocked(sr); // Do it just in case
+
+ final Message msg = mAm.mHandler.obtainMessage(
+ ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
+ mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
+ }
+
+ /**
+ * Stop the timeout for a ServiceRecord, if it's of a short-FGS.
+ */
+ private void maybeStopShortFgsTimeoutLocked(ServiceRecord sr) {
+ if (!sr.isShortFgs()) {
+ return;
+ }
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.i(TAG_SERVICE, "Stop short FGS timeout: " + sr);
+ }
+ sr.clearShortFgsInfo();
+ unscheduleShortFgsTimeoutLocked(sr);
+ }
+
+ void onShortFgsTimeout(ServiceRecord sr) {
+ synchronized (mAm) {
+ if (!sr.shouldTriggerShortFgsTimeout()) {
+ return;
+ }
+ Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+ try {
+ sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId());
+ } catch (RemoteException e) {
+ // TODO(short-service): Anything to do here?
+ }
+ // Schedule the ANR timeout.
+ final Message msg = mAm.mHandler.obtainMessage(
+ ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+ mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
+ }
+ }
+
+ void onShortFgsAnrTimeout(ServiceRecord sr) {
+ final String reason = "A foreground service of FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
+ + " did not stop within a timeout: " + sr.getComponentName();
+
+ final TimeoutRecord tr = TimeoutRecord.forShortFgsTimeout(reason);
+
+ // TODO(short-service): TODO Add SHORT_FGS_TIMEOUT to AnrLatencyTracker
+ tr.mLatencyTracker.waitingOnAMSLockStarted();
+ synchronized (mAm) {
+ tr.mLatencyTracker.waitingOnAMSLockEnded();
+
+ if (!sr.shouldTriggerShortFgsAnr()) {
+ return;
+ }
+
+ final String message = "Short FGS ANR'ed: " + sr;
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.wtf(TAG_SERVICE, message);
+ } else {
+ Slog.e(TAG_SERVICE, message);
+ }
+ mAm.appNotResponding(sr.app, tr);
+ }
+ }
+
private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
psr.mAllowlistManager = false;
for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
@@ -2897,6 +3017,7 @@
}
}
+ // TODO(short-service): Hmm what is it? Should we stop the timeout here?
private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
final ProcessServiceRecord psr = service.app.mServices;
psr.stopService(service);
@@ -4178,7 +4299,7 @@
/**
* Reschedule service restarts based on if the extra delays are enabled or not.
*
- * @param prevEnable The previous state of whether or not it's enabled.
+ * @param prevEnabled The previous state of whether or not it's enabled.
* @param curEnabled The current state of whether or not it's enabled.
* @param now The uptimeMillis
*/
@@ -4863,13 +4984,6 @@
Slog.i(TAG, "Bring down service for " + debugReason + " :" + r.toString());
}
- // TODO(short-service): Hmm, when the app stops a short-fgs, we should stop the timeout
- // here.
- // However we have a couple if's here and if these conditions are met, we stop here
- // without bringing down the service.
- // We need to make sure this can't be used (somehow) to keep having a short-FGS running
- // while having the timeout stopped.
-
if (isServiceNeededLocked(r, knowConn, hasConn)) {
return;
}
@@ -4886,6 +5000,13 @@
//Slog.i(TAG, "Bring down service:");
//r.dump(" ");
+ if (r.isShortFgs()) {
+ // FGS can be stopped without the app calling stopService() or stopSelf(),
+ // due to force-app-standby, or from Task Manager.
+ Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
+ maybeStopShortFgsTimeoutLocked(r);
+ }
+
// Report to all of the connections that the service is no longer
// available.
ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 046403d..2d69667 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -954,7 +954,7 @@
static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 60_000;
/** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
- public static volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
+ public volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
/**
* If a "short service" doesn't finish within this after the timeout (
@@ -967,7 +967,7 @@
static final long DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION = 5_000;
/** @see #KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION */
- public static volatile long mShortFgsProcStateExtraWaitDuration =
+ public volatile long mShortFgsProcStateExtraWaitDuration =
DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION;
/**
@@ -983,7 +983,7 @@
static final long DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
/** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
- public static volatile long mShortFgsAnrExtraWaitDuration =
+ public volatile long mShortFgsAnrExtraWaitDuration =
DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c779ea9..35b46c1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -206,7 +206,7 @@
import android.app.SyncNotedAppOp;
import android.app.WaitResult;
import android.app.assist.ActivityId;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.job.JobParameters;
@@ -1563,6 +1563,8 @@
static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
static final int DISPATCH_SENDING_BROADCAST_EVENT = 74;
static final int DISPATCH_BINDING_SERVICE_EVENT = 75;
+ static final int SERVICE_SHORT_FGS_TIMEOUT_MSG = 76;
+ static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 77;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1897,6 +1899,12 @@
mBindServiceEventListeners.forEach(l ->
l.onBindingService((String) msg.obj, msg.arg1));
} break;
+ case SERVICE_SHORT_FGS_TIMEOUT_MSG: {
+ mServices.onShortFgsTimeout((ServiceRecord) msg.obj);
+ } break;
+ case SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG: {
+ mServices.onShortFgsAnrTimeout((ServiceRecord) msg.obj);
+ } break;
}
}
}
@@ -5179,7 +5187,8 @@
PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
try {
thread.scheduleCreateBackupAgent(backupTarget.appInfo,
- backupTarget.backupMode, backupTarget.userId, backupTarget.operationType);
+ backupTarget.backupMode, backupTarget.userId,
+ backupTarget.backupDestination);
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
badApp = true;
@@ -6141,6 +6150,7 @@
/**
* This can be called with or without the global lock held.
*/
+ @PermissionMethod(anyOf = true)
private void enforceCallingHasAtLeastOnePermission(String func, String... permissions) {
for (String permission : permissions) {
if (checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
@@ -13129,7 +13139,7 @@
// instantiated. The backup agent will invoke backupAgentCreated() on the
// activity manager to announce its creation.
public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
- @OperationType int operationType) {
+ @BackupDestination int backupDestination) {
if (DEBUG_BACKUP) {
Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
+ " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -13196,7 +13206,7 @@
+ app.packageName + ": " + e);
}
- BackupRecord r = new BackupRecord(app, backupMode, targetUserId, operationType);
+ BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination);
ComponentName hostingName =
(backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL)
? new ComponentName(app.packageName, app.backupAgentName)
@@ -13238,7 +13248,7 @@
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
try {
thread.scheduleCreateBackupAgent(app, backupMode, targetUserId,
- operationType);
+ backupDestination);
} catch (RemoteException e) {
// Will time out on the backup manager side
}
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index d419856..0b056d7 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -16,8 +16,7 @@
package com.android.server.am;
-import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.content.pm.ApplicationInfo;
/** @hide */
@@ -32,16 +31,16 @@
final ApplicationInfo appInfo; // information about BackupAgent's app
final int userId; // user for which backup is performed
final int backupMode; // full backup / incremental / restore
- @OperationType final int operationType; // see BackupManager#OperationType
+ @BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination
ProcessRecord app; // where this agent is running or null
// ----- Implementation -----
- BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _operationType) {
+ BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) {
appInfo = _appInfo;
backupMode = _backupMode;
userId = _userId;
- operationType = _operationType;
+ backupDestination = _backupDestination;
}
public String toString() {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 8082e45..66a8bab 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1818,21 +1818,31 @@
newAdj = PERCEPTIBLE_APP_ADJ;
newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- } else if (psr.hasForegroundServices() && !psr.hasNonShortForegroundServices()) {
- // For short FGS.
- adjType = "fg-service-short";
- // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ (which uses MEDIUM_APP_ADJ + 1)
- // from short-FGS.
- // (We use +1 and +2, not +0 and +1, to be consistent with the following
- // RECENT_FOREGROUND_APP_ADJ tweak)
- newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 1;
+ } else if (psr.hasForegroundServices()) {
+ // If we get here, hasNonShortForegroundServices() must be false.
- // Short-FGS gets a below-BFGS procstate, so it can't start another FGS from it.
- newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+ // TODO(short-service): Proactively run OomAjudster when the grace period finish.
+ if (psr.areAllShortForegroundServicesProcstateTimedOut(now)) {
+ // All the short-FGSes within this process are timed out. Don't promote to FGS.
+ // TODO(short-service): Should we set some unique oom-adj to make it detectable,
+ // in a long trace?
+ } else {
+ // For short FGS.
+ adjType = "fg-service-short";
+ // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ
+ // (which uses MEDIUM_APP_ADJ + 1)
+ // from short-FGS.
+ // (We use +1 and +2, not +0 and +1, to be consistent with the following
+ // RECENT_FOREGROUND_APP_ADJ tweak)
+ newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 1;
- // Same as EJ, we explicitly grant network access to short FGS,
- // even when battery saver or data saver is enabled.
- capabilityFromFGS |= PROCESS_CAPABILITY_NETWORK;
+ // Short-FGS gets a below-BFGS procstate, so it can't start another FGS from it.
+ newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+
+ // Same as EJ, we explicitly grant network access to short FGS,
+ // even when battery saver or data saver is enabled.
+ capabilityFromFGS |= PROCESS_CAPABILITY_NETWORK;
+ }
}
if (adjType != null) {
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 13264db..df442e8 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -175,6 +175,9 @@
}
}
+ /**
+ * @return true if this process has any foreground services (even timed-out short-FGS)
+ */
boolean hasForegroundServices() {
return mHasForegroundServices;
}
@@ -224,6 +227,33 @@
return mFgServiceTypes != ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
+ /**
+ * @return if this process:
+ * - has at least one short-FGS
+ * - has no other types of FGS
+ * - and all the short-FGSes are procstate-timed out.
+ */
+ boolean areAllShortForegroundServicesProcstateTimedOut(long nowUptime) {
+ if (!mHasForegroundServices) { // Process has no FGS?
+ return false;
+ }
+ if (hasNonShortForegroundServices()) { // Any non-short FGS running?
+ return false;
+ }
+ // Now we need to look at all short-FGS within the process and see if all of them are
+ // procstate-timed-out or not.
+ for (int i = mServices.size() - 1; i >= 0; i--) {
+ final ServiceRecord sr = mServices.valueAt(i);
+ if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
+ continue;
+ }
+ if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
+ return false;
+ }
+ }
+ return true;
+ }
+
int getReportedForegroundServiceTypes() {
return mRepFgServiceTypes;
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 0468152..547c20b 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -315,6 +315,87 @@
final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>();
// start() arguments that haven't yet been delivered.
+ /**
+ * Information specific to "SHORT_SERVICE" FGS.
+ */
+ class ShortFgsInfo {
+ /** Time FGS started */
+ private final long mStartTime;
+
+ /**
+ * Copied from {@link #mStartForegroundCount}. If this is different from the parent's,
+ * that means this instance is stale.
+ */
+ private int mStartForegroundCount;
+
+ /** Service's "start ID" when this short-service started. */
+ private int mStartId;
+
+ ShortFgsInfo(long startTime) {
+ mStartTime = startTime;
+ update();
+ }
+
+ /**
+ * Update {@link #mStartForegroundCount} and {@link #mStartId}.
+ * (but not {@link #mStartTime})
+ */
+ public void update() {
+ this.mStartForegroundCount = ServiceRecord.this.mStartForegroundCount;
+ this.mStartId = getLastStartId();
+ }
+
+ long getStartTime() {
+ return mStartTime;
+ }
+
+ int getStartForegroundCount() {
+ return mStartForegroundCount;
+ }
+
+ int getStartId() {
+ return mStartId;
+ }
+
+ /**
+ * @return whether this {@link ShortFgsInfo} is still "current" or not -- i.e.
+ * it's "start foreground count" is the same as that of the ServiceRecord's.
+ *
+ * Note, we do _not_ check the "start id" here, because the start id increments if the
+ * app calls startService() or startForegroundService() on the same service,
+ * but that will _not_ update the ShortFgsInfo, and will not extend the timeout.
+ *
+ * TODO(short-service): Make sure, calling startService will not extend or remove the
+ * timeout, in CTS.
+ */
+ boolean isCurrent() {
+ return this.mStartForegroundCount == ServiceRecord.this.mStartForegroundCount;
+ }
+
+ /** Time when Service.onTimeout() should be called */
+ long getTimeoutTime() {
+ return mStartTime + ams.mConstants.mShortFgsTimeoutDuration;
+ }
+
+ /** Time when the procstate should be lowered. */
+ long getProcStateDemoteTime() {
+ return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
+ + ams.mConstants.mShortFgsProcStateExtraWaitDuration;
+ }
+
+ /** Time when the app should be declared ANR. */
+ long getAnrTime() {
+ return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
+ + ams.mConstants.mShortFgsAnrExtraWaitDuration;
+ }
+ }
+
+ /**
+ * Keep track of short-fgs specific information. This field gets cleared when the timeout
+ * stops.
+ */
+ private ShortFgsInfo mShortFgsInfo;
+
void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
final int N = list.size();
for (int i=0; i<N; i++) {
@@ -456,6 +537,8 @@
}
}
proto.end(token);
+
+ // TODO(short-service) Add FGS info
}
void dump(PrintWriter pw, String prefix) {
@@ -508,8 +591,25 @@
}
if (isForeground || foregroundId != 0) {
pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
- pw.print(" foregroundId="); pw.print(foregroundId);
- pw.print(" foregroundNoti="); pw.println(foregroundNoti);
+ pw.print(" foregroundId="); pw.print(foregroundId);
+ pw.printf(" types=%08X", foregroundServiceType);
+ pw.print(" foregroundNoti="); pw.println(foregroundNoti);
+
+ if (isShortFgs() && mShortFgsInfo != null) {
+ pw.print(prefix); pw.print("isShortFgs=true");
+ pw.print(" startId="); pw.print(mShortFgsInfo.getStartId());
+ pw.print(" startForegroundCount=");
+ pw.print(mShortFgsInfo.getStartForegroundCount());
+ pw.print(" startTime=");
+ TimeUtils.formatDuration(mShortFgsInfo.getStartTime(), now, pw);
+ pw.print(" timeout=");
+ TimeUtils.formatDuration(mShortFgsInfo.getTimeoutTime(), now, pw);
+ pw.print(" demoteTime=");
+ TimeUtils.formatDuration(mShortFgsInfo.getProcStateDemoteTime(), now, pw);
+ pw.print(" anrTime=");
+ TimeUtils.formatDuration(mShortFgsInfo.getAnrTime(), now, pw);
+ pw.println();
+ }
}
if (mIsFgsDelegate) {
pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
@@ -590,6 +690,32 @@
}
}
+ /** Used only for tests */
+ private ServiceRecord(ActivityManagerService ams) {
+ this.ams = ams;
+ name = null;
+ instanceName = null;
+ shortInstanceName = null;
+ definingPackageName = null;
+ definingUid = 0;
+ intent = null;
+ serviceInfo = null;
+ userId = 0;
+ packageName = null;
+ processName = null;
+ permission = null;
+ exported = false;
+ restarter = null;
+ createRealTime = 0;
+ isSdkSandbox = false;
+ sdkSandboxClientAppUid = 0;
+ sdkSandboxClientAppPackage = null;
+ }
+
+ public static ServiceRecord newEmptyInstanceForTest(ActivityManagerService ams) {
+ return new ServiceRecord(ams);
+ }
+
ServiceRecord(ActivityManagerService ams, ComponentName name,
ComponentName instanceName, String definingPackageName, int definingUid,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
@@ -1238,4 +1364,66 @@
return isForeground
&& (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
}
+
+ public ShortFgsInfo getShortFgsInfo() {
+ return isShortFgs() ? mShortFgsInfo : null;
+ }
+
+ /**
+ * Call it when a short FGS starts.
+ */
+ public void setShortFgsInfo(long uptimeNow) {
+ this.mShortFgsInfo = new ShortFgsInfo(uptimeNow);
+ }
+
+ /** @return whether {@link #mShortFgsInfo} is set or not. */
+ public boolean hasShortFgsInfo() {
+ return mShortFgsInfo != null;
+ }
+
+ /**
+ * Call it when a short FGS stops.
+ */
+ public void clearShortFgsInfo() {
+ this.mShortFgsInfo = null;
+ }
+
+ /**
+ * @return true if it's a short FGS that's still up and running, and should be timed out.
+ */
+ public boolean shouldTriggerShortFgsTimeout() {
+ if (!isAppAlive()) {
+ return false;
+ }
+ if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
+ || !mShortFgsInfo.isCurrent()) {
+ return false;
+ }
+ return mShortFgsInfo.getTimeoutTime() < SystemClock.uptimeMillis();
+ }
+
+ /**
+ * @return true if it's a short FGS that's still up and running, and should be declared
+ * an ANR.
+ */
+ public boolean shouldTriggerShortFgsAnr() {
+ if (!isAppAlive()) {
+ return false;
+ }
+ if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
+ || !mShortFgsInfo.isCurrent()) {
+ return false;
+ }
+ return mShortFgsInfo.getAnrTime() < SystemClock.uptimeMillis();
+ }
+
+ private boolean isAppAlive() {
+ if (app == null) {
+ return false;
+ }
+ if (app.getThread() == null || app.isKilled() || app.isKilledByAm()) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index d39d2d1..1b20e43 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -18,9 +18,9 @@
import android.app.IWallpaperManager;
import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupHelper;
-import android.app.backup.BackupManager;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.WallpaperBackupHelper;
@@ -89,8 +89,8 @@
private int mUserId = UserHandle.USER_SYSTEM;
@Override
- public void onCreate(UserHandle user, @BackupManager.OperationType int operationType) {
- super.onCreate(user, operationType);
+ public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
+ super.onCreate(user, backupDestination);
mUserId = user.getIdentifier();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 1c57151..229393d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -421,13 +421,6 @@
return -1;
}
- if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
- // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
- // ever be invoked when the user is encrypted or lockdown.
- Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return -1;
- }
-
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index c83fa2d..c99a7a0 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -19,7 +19,13 @@
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.BatteryState;
import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
@@ -46,6 +52,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -74,6 +81,7 @@
private final NativeInputManagerService mNative;
private final Handler mHandler;
private final UEventManager mUEventManager;
+ private final BluetoothBatteryManager mBluetoothBatteryManager;
// Maps a pid to the registered listener record for that process. There can only be one battery
// listener per process.
@@ -88,18 +96,23 @@
private boolean mIsPolling = false;
@GuardedBy("mLock")
private boolean mIsInteractive = true;
+ @Nullable
+ @GuardedBy("mLock")
+ private BluetoothBatteryManager.BluetoothBatteryListener mBluetoothBatteryListener;
BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
- this(context, nativeService, looper, new UEventManager() {});
+ this(context, nativeService, looper, new UEventManager() {},
+ new LocalBluetoothBatteryManager(context));
}
@VisibleForTesting
BatteryController(Context context, NativeInputManagerService nativeService, Looper looper,
- UEventManager uEventManager) {
+ UEventManager uEventManager, BluetoothBatteryManager bbm) {
mContext = context;
mNative = nativeService;
mHandler = new Handler(looper);
mUEventManager = uEventManager;
+ mBluetoothBatteryManager = bbm;
}
public void systemRunning() {
@@ -150,6 +163,7 @@
// This is the first listener that is monitoring this device.
monitor = new DeviceMonitor(deviceId);
mDeviceMonitors.put(deviceId, monitor);
+ updateBluetoothMonitoring();
}
if (DEBUG) {
@@ -202,25 +216,39 @@
mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0);
}
- private String getInputDeviceName(int deviceId) {
+ private <R> R processInputDevice(int deviceId, R defaultValue, Function<InputDevice, R> func) {
final InputDevice device =
Objects.requireNonNull(mContext.getSystemService(InputManager.class))
.getInputDevice(deviceId);
- return device != null ? device.getName() : "<none>";
+ return device == null ? defaultValue : func.apply(device);
+ }
+
+ private String getInputDeviceName(int deviceId) {
+ return processInputDevice(deviceId, "<none>" /*defaultValue*/, InputDevice::getName);
}
private boolean hasBattery(int deviceId) {
- final InputDevice device =
- Objects.requireNonNull(mContext.getSystemService(InputManager.class))
- .getInputDevice(deviceId);
- return device != null && device.hasBattery();
+ return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::hasBattery);
}
private boolean isUsiDevice(int deviceId) {
- final InputDevice device =
- Objects.requireNonNull(mContext.getSystemService(InputManager.class))
- .getInputDevice(deviceId);
- return device != null && device.supportsUsi();
+ return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::supportsUsi);
+ }
+
+ @Nullable
+ private BluetoothDevice getBluetoothDevice(int inputDeviceId) {
+ return getBluetoothDevice(mContext,
+ processInputDevice(inputDeviceId, null /*defaultValue*/,
+ InputDevice::getBluetoothAddress));
+ }
+
+ @Nullable
+ private static BluetoothDevice getBluetoothDevice(Context context, String address) {
+ if (address == null) return null;
+ final BluetoothAdapter adapter =
+ Objects.requireNonNull(context.getSystemService(BluetoothManager.class))
+ .getAdapter();
+ return adapter.getRemoteDevice(address);
}
@GuardedBy("mLock")
@@ -350,6 +378,17 @@
}
}
+ private void handleBluetoothBatteryLevelChange(long eventTime, String address) {
+ synchronized (mLock) {
+ final DeviceMonitor monitor = findIf(mDeviceMonitors, (m) ->
+ (m.mBluetoothDevice != null
+ && address.equals(m.mBluetoothDevice.getAddress())));
+ if (monitor != null) {
+ monitor.onBluetoothBatteryChanged(eventTime);
+ }
+ }
+ }
+
/** Gets the current battery state of an input device. */
public IInputDeviceBatteryState getBatteryState(int deviceId) {
synchronized (mLock) {
@@ -475,17 +514,52 @@
isPresent ? mNative.getBatteryCapacity(deviceId) / 100.f : Float.NaN);
}
+ // Queries the battery state of an input device from Bluetooth.
+ private State queryBatteryStateFromBluetooth(int deviceId, long updateTime,
+ @NonNull BluetoothDevice bluetoothDevice) {
+ final int level = mBluetoothBatteryManager.getBatteryLevel(bluetoothDevice.getAddress());
+ if (level == BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF
+ || level == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ return new State(deviceId);
+ }
+ return new State(deviceId, updateTime, true /*isPresent*/, BatteryState.STATUS_UNKNOWN,
+ level / 100.f);
+ }
+
+ private void updateBluetoothMonitoring() {
+ synchronized (mLock) {
+ if (anyOf(mDeviceMonitors, (m) -> m.mBluetoothDevice != null)) {
+ // At least one input device being monitored is connected over Bluetooth.
+ if (mBluetoothBatteryListener == null) {
+ if (DEBUG) Slog.d(TAG, "Registering bluetooth battery listener");
+ mBluetoothBatteryListener = this::handleBluetoothBatteryLevelChange;
+ mBluetoothBatteryManager.addListener(mBluetoothBatteryListener);
+ }
+ } else if (mBluetoothBatteryListener != null) {
+ // No Bluetooth input devices are monitored, so remove the registered listener.
+ if (DEBUG) Slog.d(TAG, "Unregistering bluetooth battery listener");
+ mBluetoothBatteryManager.removeListener(mBluetoothBatteryListener);
+ mBluetoothBatteryListener = null;
+ }
+ }
+ }
+
// Holds the state of an InputDevice for which battery changes are currently being monitored.
private class DeviceMonitor {
protected final State mState;
// Represents whether the input device has a sysfs battery node.
protected boolean mHasBattery = false;
+ protected final State mBluetoothState;
+ @Nullable
+ private BluetoothDevice mBluetoothDevice;
+
@Nullable
private UEventBatteryListener mUEventBatteryListener;
DeviceMonitor(int deviceId) {
mState = new State(deviceId);
+ mBluetoothState = new State(deviceId);
// Load the initial battery state and start monitoring.
final long eventTime = SystemClock.uptimeMillis();
@@ -506,18 +580,31 @@
}
private void configureDeviceMonitor(long eventTime) {
+ final int deviceId = mState.deviceId;
if (mHasBattery != hasBattery(mState.deviceId)) {
mHasBattery = !mHasBattery;
if (mHasBattery) {
- startMonitoring();
+ startNativeMonitoring();
} else {
- stopMonitoring();
+ stopNativeMonitoring();
}
updateBatteryStateFromNative(eventTime);
}
+
+ final BluetoothDevice bluetoothDevice = getBluetoothDevice(deviceId);
+ if (!Objects.equals(mBluetoothDevice, bluetoothDevice)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Bluetooth device "
+ + ((bluetoothDevice != null) ? "is" : "is not")
+ + " now present for deviceId " + deviceId);
+ }
+ mBluetoothDevice = bluetoothDevice;
+ updateBluetoothMonitoring();
+ updateBatteryStateFromBluetooth(eventTime);
+ }
}
- private void startMonitoring() {
+ private void startNativeMonitoring() {
final String batteryPath = mNative.getBatteryDevicePath(mState.deviceId);
if (batteryPath == null) {
return;
@@ -538,7 +625,7 @@
return path.startsWith("/sys") ? path.substring(4) : path;
}
- private void stopMonitoring() {
+ private void stopNativeMonitoring() {
if (mUEventBatteryListener != null) {
mUEventManager.removeListener(mUEventBatteryListener);
mUEventBatteryListener = null;
@@ -547,7 +634,9 @@
// This must be called when the device is no longer being monitored.
public void onMonitorDestroy() {
- stopMonitoring();
+ stopNativeMonitoring();
+ mBluetoothDevice = null;
+ updateBluetoothMonitoring();
}
protected void updateBatteryStateFromNative(long eventTime) {
@@ -555,6 +644,13 @@
queryBatteryStateFromNative(mState.deviceId, eventTime, mHasBattery));
}
+ protected void updateBatteryStateFromBluetooth(long eventTime) {
+ final State bluetoothState = mBluetoothDevice == null ? new State(mState.deviceId)
+ : queryBatteryStateFromBluetooth(mState.deviceId, eventTime,
+ mBluetoothDevice);
+ mBluetoothState.updateIfChanged(bluetoothState);
+ }
+
public void onPoll(long eventTime) {
processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
}
@@ -563,6 +659,10 @@
processChangesAndNotify(eventTime, this::updateBatteryStateFromNative);
}
+ public void onBluetoothBatteryChanged(long eventTime) {
+ processChangesAndNotify(eventTime, this::updateBatteryStateFromBluetooth);
+ }
+
public boolean requiresPolling() {
return true;
}
@@ -577,6 +677,10 @@
// Returns the current battery state that can be used to notify listeners BatteryController.
public State getBatteryStateForReporting() {
+ // Give precedence to the Bluetooth battery state if it's present.
+ if (mBluetoothState.isPresent) {
+ return new State(mBluetoothState);
+ }
return new State(mState);
}
@@ -585,7 +689,8 @@
return "DeviceId=" + mState.deviceId
+ ", Name='" + getInputDeviceName(mState.deviceId) + "'"
+ ", NativeBattery=" + mState
- + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none");
+ + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none")
+ + ", BluetoothBattery=" + mBluetoothState;
}
}
@@ -670,6 +775,10 @@
@Override
public State getBatteryStateForReporting() {
+ // Give precedence to the Bluetooth battery state if it's present.
+ if (mBluetoothState.isPresent) {
+ return new State(mBluetoothState);
+ }
return mValidityTimeoutCallback != null
? new State(mState) : new State(mState.deviceId);
}
@@ -729,6 +838,82 @@
}
}
+ // An interface used to change the API of adding a bluetooth battery listener to a more
+ // test-friendly format.
+ @VisibleForTesting
+ interface BluetoothBatteryManager {
+ @VisibleForTesting
+ interface BluetoothBatteryListener {
+ void onBluetoothBatteryChanged(long eventTime, String address);
+ }
+ void addListener(BluetoothBatteryListener listener);
+ void removeListener(BluetoothBatteryListener listener);
+ int getBatteryLevel(String address);
+ }
+
+ private static class LocalBluetoothBatteryManager implements BluetoothBatteryManager {
+ private final Context mContext;
+ @Nullable
+ @GuardedBy("mBroadcastReceiver")
+ private BluetoothBatteryListener mRegisteredListener;
+ @GuardedBy("mBroadcastReceiver")
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED.equals(intent.getAction())) {
+ return;
+ }
+ final BluetoothDevice bluetoothDevice = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
+ if (bluetoothDevice == null) {
+ return;
+ }
+ // We do not use the EXTRA_LEVEL value. Instead, the battery level will be queried
+ // from BluetoothDevice later so that we use a single source for the battery level.
+ synchronized (mBroadcastReceiver) {
+ if (mRegisteredListener != null) {
+ final long eventTime = SystemClock.uptimeMillis();
+ mRegisteredListener.onBluetoothBatteryChanged(
+ eventTime, bluetoothDevice.getAddress());
+ }
+ }
+ }
+ };
+
+ LocalBluetoothBatteryManager(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void addListener(BluetoothBatteryListener listener) {
+ synchronized (mBroadcastReceiver) {
+ if (mRegisteredListener != null) {
+ throw new IllegalStateException(
+ "Only one bluetooth battery listener can be registered at once.");
+ }
+ mRegisteredListener = listener;
+ mContext.registerReceiver(mBroadcastReceiver,
+ new IntentFilter(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED));
+ }
+ }
+
+ @Override
+ public void removeListener(BluetoothBatteryListener listener) {
+ synchronized (mBroadcastReceiver) {
+ if (!listener.equals(mRegisteredListener)) {
+ throw new IllegalStateException("Listener is not registered.");
+ }
+ mRegisteredListener = null;
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+ }
+
+ @Override
+ public int getBatteryLevel(String address) {
+ return getBluetoothDevice(mContext, address).getBatteryLevel();
+ }
+ }
+
// Helper class that adds copying and printing functionality to IInputDeviceBatteryState.
private static class State extends IInputDeviceBatteryState {
@@ -792,11 +977,17 @@
// Check if any value in an ArrayMap matches the predicate in an optimized way.
private static <K, V> boolean anyOf(ArrayMap<K, V> arrayMap, Predicate<V> test) {
+ return findIf(arrayMap, test) != null;
+ }
+
+ // Find the first value in an ArrayMap that matches the predicate in an optimized way.
+ private static <K, V> V findIf(ArrayMap<K, V> arrayMap, Predicate<V> test) {
for (int i = 0; i < arrayMap.size(); i++) {
- if (test.test(arrayMap.valueAt(i))) {
- return true;
+ final V value = arrayMap.valueAt(i);
+ if (test.test(value)) {
+ return value;
}
}
- return false;
+ return null;
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1274f02..199519c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2126,7 +2126,13 @@
public String getInputDeviceBluetoothAddress(int deviceId) {
super.getInputDeviceBluetoothAddress_enforcePermission();
- return mNative.getBluetoothAddress(deviceId);
+ final String address = mNative.getBluetoothAddress(deviceId);
+ if (address == null) return null;
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ throw new IllegalStateException("The Bluetooth address of input device " + deviceId
+ + " should not be invalid: address=" + address);
+ }
+ return address;
}
@EnforcePermission(Manifest.permission.MONITOR_INPUT)
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2669d21..8d247f6 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -140,9 +140,7 @@
import com.android.server.location.provider.proxy.ProxyLocationProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -310,10 +308,6 @@
permissionManagerInternal.setLocationExtraPackagesProvider(
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationExtraPackageNames));
-
- // TODO(b/241604546): properly handle this callback
- LocalServices.getService(UserManagerInternal.class).addUserVisibilityListener(
- (u, v) -> Slogf.i(TAG, "onUserVisibilityChanged(): %d -> %b", u, v));
}
@Nullable
@@ -1702,7 +1696,7 @@
private final Context mContext;
- private final UserInfoHelper mUserInfoHelper;
+ private final SystemUserInfoHelper mUserInfoHelper;
private final LocationSettings mLocationSettings;
private final AlarmHelper mAlarmHelper;
private final SystemAppOpsHelper mAppOpsHelper;
@@ -1725,7 +1719,7 @@
@GuardedBy("this")
private boolean mSystemReady;
- SystemInjector(Context context, UserInfoHelper userInfoHelper) {
+ SystemInjector(Context context, SystemUserInfoHelper userInfoHelper) {
mContext = context;
mUserInfoHelper = userInfoHelper;
@@ -1745,6 +1739,7 @@
}
synchronized void onSystemReady() {
+ mUserInfoHelper.onSystemReady();
mAppOpsHelper.onSystemReady();
mLocationPermissionsHelper.onSystemReady();
mSettingsHelper.onSystemReady();
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 45436e7..cb952ed 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -110,6 +110,11 @@
addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
}
+ /** Logs a user visibility changed event. */
+ public void logUserVisibilityChanged(int userId, boolean visible) {
+ addLog(new UserVisibilityChangedEvent(userId, visible));
+ }
+
/** Logs a location enabled/disabled event. */
public void logLocationEnabled(int userId, boolean enabled) {
addLog(new LocationEnabledEvent(userId, enabled));
@@ -475,6 +480,22 @@
}
}
+ private static final class UserVisibilityChangedEvent {
+
+ private final int mUserId;
+ private final boolean mVisible;
+
+ UserVisibilityChangedEvent(int userId, boolean visible) {
+ mUserId = userId;
+ mVisible = visible;
+ }
+
+ @Override
+ public String toString() {
+ return "[u" + mUserId + "] " + (mVisible ? "visible" : "invisible");
+ }
+ }
+
private static final class LocationEnabledEvent {
private final int mUserId;
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 0f5e3d4..d3ceddd 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -387,7 +387,7 @@
if (!mSettingsHelper.isLocationEnabled(identity.getUserId())) {
return false;
}
- if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+ if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
return false;
}
if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -534,7 +534,10 @@
}
void onUserChanged(int userId, int change) {
- if (change == UserListener.CURRENT_USER_CHANGED) {
+ // current user changes affect whether system server location requests are allowed to access
+ // location, and visibility changes affect whether any given user may access location.
+ if (change == UserListener.CURRENT_USER_CHANGED
+ || change == UserListener.USER_VISIBILITY_CHANGED) {
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 349b94b..567d8ac 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -317,7 +317,7 @@
identity.getUserId())) {
return false;
}
- if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
+ if (!mUserInfoHelper.isVisibleUserId(identity.getUserId())) {
return false;
}
if (mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -394,7 +394,10 @@
}
private void onUserChanged(int userId, int change) {
- if (change == UserListener.CURRENT_USER_CHANGED) {
+ // current user changes affect whether system server location requests are allowed to access
+ // location, and visibility changes affect whether any given user may access location.
+ if (change == UserListener.CURRENT_USER_CHANGED
+ || change == UserListener.USER_VISIBILITY_CHANGED) {
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
}
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index ed1e654..40dd979 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -33,9 +33,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.util.Arrays;
+import java.util.Objects;
/**
* Provides accessors and listeners for all user info.
@@ -50,11 +52,21 @@
@Nullable private IActivityManager mActivityManager;
@GuardedBy("this")
@Nullable private UserManager mUserManager;
+ @GuardedBy("this")
+ @Nullable private UserManagerInternal mUserManagerInternal;
public SystemUserInfoHelper(Context context) {
mContext = context;
}
+ /** The function should be called when PHASE_SYSTEM_SERVICES_READY. */
+ public synchronized void onSystemReady() {
+ mUserManagerInternal =
+ Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class));
+ mUserManagerInternal.addUserVisibilityListener(
+ (userId, visible) -> dispatchOnVisibleUserChanged(userId, visible));
+ }
+
@Nullable
protected final ActivityManagerInternal getActivityManagerInternal() {
synchronized (this) {
@@ -136,6 +148,24 @@
}
@Override
+ public boolean isVisibleUserId(@UserIdInt int userId) {
+ synchronized (this) {
+ // if you're hitting this precondition then you are invoking this before the system is
+ // ready
+ Preconditions.checkState(mUserManagerInternal != null);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ return mUserManagerInternal.isUserVisible(userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
protected int[] getProfileIds(@UserIdInt int userId) {
UserManager userManager = getUserManager();
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index c835370..2b9db1c 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -22,6 +22,7 @@
import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
+import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_VISIBILITY_CHANGED;
import android.annotation.IntDef;
import android.annotation.UserIdInt;
@@ -47,8 +48,9 @@
int CURRENT_USER_CHANGED = 1;
int USER_STARTED = 2;
int USER_STOPPED = 3;
+ int USER_VISIBILITY_CHANGED = 4;
- @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED})
+ @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED, USER_VISIBILITY_CHANGED})
@Retention(RetentionPolicy.SOURCE)
@interface UserChange {}
@@ -121,6 +123,18 @@
}
}
+ protected final void dispatchOnVisibleUserChanged(@UserIdInt int userId, boolean visible) {
+ if (D) {
+ Log.d(TAG, "visibility of u" + userId + " changed to "
+ + (visible ? "visible" : "invisible"));
+ }
+ EVENT_LOG.logUserVisibilityChanged(userId, visible);
+
+ for (UserListener listener : mListeners) {
+ listener.onUserChanged(userId, USER_VISIBILITY_CHANGED);
+ }
+ }
+
/**
* Returns an array of running user ids. This will include all running users, and will also
* include any profiles of the running users. The caller must never mutate the returned
@@ -129,8 +143,8 @@
public abstract int[] getRunningUserIds();
/**
- * Returns true if the given user id is either the current user or a profile of the current
- * user.
+ * Returns {@code true} if the given user id is either the current user or a profile of the
+ * current user.
*/
public abstract boolean isCurrentUserId(@UserIdInt int userId);
@@ -140,6 +154,13 @@
*/
public abstract @UserIdInt int getCurrentUserId();
+ /**
+ * Returns {@code true} if the user is visible.
+ *
+ * <p>The visibility of a user is defined by {@link android.os.UserManager#isUserVisible()}.
+ */
+ public abstract boolean isVisibleUserId(@UserIdInt int userId);
+
protected abstract int[] getProfileIds(@UserIdInt int userId);
/**
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 338a995..7063cb8 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -661,6 +661,8 @@
if (!GPS_PROVIDER.equals(mName)) {
Log.e(TAG, "adas gnss bypass request received in non-gps provider");
adasGnssBypass = false;
+ } else if (!mUserHelper.isCurrentUserId(getIdentity().getUserId())) {
+ adasGnssBypass = false;
} else if (!mLocationSettings.getUserSettings(
getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
@@ -1712,6 +1714,8 @@
if (!GPS_PROVIDER.equals(mName)) {
Log.e(TAG, "adas gnss bypass request received in non-gps provider");
adasGnssBypass = false;
+ } else if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+ adasGnssBypass = false;
} else if (!mLocationSettings.getUserSettings(
identity.getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
@@ -2193,7 +2197,7 @@
if (!isEnabled(identity.getUserId())) {
return false;
}
- if (!mUserHelper.isCurrentUserId(identity.getUserId())) {
+ if (!mUserHelper.isVisibleUserId(identity.getUserId())) {
return false;
}
}
@@ -2322,6 +2326,10 @@
switch (change) {
case UserListener.CURRENT_USER_CHANGED:
+ // current user changes affect whether system server location requests are
+ // allowed to access location, and visibility changes affect whether any given
+ // user may access location.
+ case UserListener.USER_VISIBILITY_CHANGED:
updateRegistrations(
registration -> registration.getIdentity().getUserId() == userId);
break;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 026c007..d6846be 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -667,9 +667,11 @@
"userId: %d", newActiveUserId));
mCurrentActiveUserId = newActiveUserId;
- for (int i = 0; i < mUserRecords.size(); i++) {
- int userId = mUserRecords.keyAt(i);
- UserRecord userRecord = mUserRecords.valueAt(i);
+ // disposeUserIfNeededLocked might modify the collection, hence clone
+ final var userRecords = mUserRecords.clone();
+ for (int i = 0; i < userRecords.size(); i++) {
+ int userId = userRecords.keyAt(i);
+ UserRecord userRecord = userRecords.valueAt(i);
if (isUserActiveLocked(userId)) {
// userId corresponds to the active user, or one of its profiles. We
// ensure the associated structures are initialized.
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index add1135..beab5ea 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -638,9 +638,11 @@
synchronized (mLock) {
if (mCurrentActiveUserId != newActiveUserId) {
mCurrentActiveUserId = newActiveUserId;
- for (int i = 0; i < mUserRecords.size(); i++) {
- int userId = mUserRecords.keyAt(i);
- UserRecord userRecord = mUserRecords.valueAt(i);
+ // disposeUserIfNeededLocked might modify the collection, hence clone
+ final var userRecords = mUserRecords.clone();
+ for (int i = 0; i < userRecords.size(); i++) {
+ int userId = userRecords.keyAt(i);
+ UserRecord userRecord = userRecords.valueAt(i);
if (isUserActiveLocked(userId)) {
// userId corresponds to the active user, or one of its profiles. We
// ensure the associated structures are initialized.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d6b9bd5..90135ad 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3797,13 +3797,13 @@
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList, boolean fromTargetApp) {
- createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp,
+ ParceledListSlice channelsList) {
+ createNotificationChannelsImpl(pkg, uid, channelsList,
ActivityTaskManager.INVALID_TASK_ID);
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) {
+ ParceledListSlice channelsList, int startingTaskId) {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
ParceledListSlice<NotificationChannel> oldChannels =
@@ -3815,7 +3815,7 @@
final NotificationChannel channel = channels.get(i);
Objects.requireNonNull(channel, "channel in list is null");
needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
- channel, fromTargetApp,
+ channel, true /* fromTargetApp */,
mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
if (needsPolicyFileChange) {
@@ -3851,7 +3851,6 @@
@Override
public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
- boolean fromTargetApp = !isCallerSystemOrPhone(); // if not system, it's from the app
int taskId = ActivityTaskManager.INVALID_TASK_ID;
try {
int uid = mPackageManager.getPackageUid(pkg, 0,
@@ -3860,15 +3859,14 @@
} catch (RemoteException e) {
// Do nothing
}
- createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp,
- taskId);
+ createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
}
@Override
public void createNotificationChannelsForPackage(String pkg, int uid,
ParceledListSlice channelsList) {
enforceSystemOrSystemUI("only system can call this");
- createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */);
+ createNotificationChannelsImpl(pkg, uid, channelsList);
}
@Override
@@ -3883,8 +3881,7 @@
CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
conversationChannel.setConversationId(parentId, conversationId);
createNotificationChannelsImpl(
- pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)),
- false /* fromTargetApp */);
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
mRankingHandler.requestSort();
handleSavePolicyFile();
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 444fef6..1bbcc83 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -918,7 +918,7 @@
throw new IllegalArgumentException("Reserved id");
}
NotificationChannel existing = r.channels.get(channel.getId());
- if (existing != null) {
+ if (existing != null && fromTargetApp) {
// Actually modifying an existing channel - keep most of the existing settings
if (existing.isDeleted()) {
// The existing channel was deleted - undelete it.
@@ -1004,7 +1004,9 @@
}
if (fromTargetApp) {
channel.setLockscreenVisibility(r.visibility);
- channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ channel.setAllowBubbles(existing != null
+ ? existing.getAllowBubbles()
+ : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
}
clearLockedFieldsLocked(channel);
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 9c4187b..2650b23 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -413,41 +413,12 @@
if (displayId == Display.INVALID_DISPLAY) {
return false;
}
- if (!mUsersOnSecondaryDisplaysEnabled) {
- return isCurrentUserOrRunningProfileOfCurrentUser(userId);
- }
- // TODO(b/256242848): temporary workaround to let WM use this API without breaking current
- // behavior - return true for current user / profile for any display (other than those
- // explicitly assigned to another users), otherwise they wouldn't be able to launch
- // activities on other non-passenger displays, like cluster).
- // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
- // would be updated by CarService to allow additional mappings.
- if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- synchronized (mLock) {
- boolean assignedToUser = false;
- boolean assignedToAnotherUser = false;
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
- if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
- assignedToUser = true;
- break;
- } else {
- assignedToAnotherUser = true;
- // Cannot break because it could be assigned to a profile of the user
- // (and we better not assume that the iteration will check for the
- // parent user before its profiles)
- }
- }
- }
- if (DBG) {
- Slogf.d(TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
- + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
- userId, displayId, assignedToUser, assignedToAnotherUser,
- mUsersOnSecondaryDisplays);
- }
- return assignedToUser || !assignedToAnotherUser;
- }
+ if (!mUsersOnSecondaryDisplaysEnabled || displayId == Display.DEFAULT_DISPLAY) {
+ // TODO(b/245939659): will need to move the displayId == Display.DEFAULT_DISPLAY outside
+ // once it supports background users on DEFAULT_DISPLAY (for example, passengers in a
+ // no-driver configuration)
+ return isCurrentUserOrRunningProfileOfCurrentUser(userId);
}
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 12d0f3c..fa811ef 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -1050,15 +1050,31 @@
TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
createTelephonyAlgorithmStatus(currentConfigurationInternal);
- LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
- latestLocationAlgorithmEvent == null ? LocationTimeZoneAlgorithmStatus.UNKNOWN
- : latestLocationAlgorithmEvent.getAlgorithmStatus();
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus = createLocationAlgorithmStatus(
+ currentConfigurationInternal, latestLocationAlgorithmEvent);
return new TimeZoneDetectorStatus(
detectorStatus, telephonyAlgorithmStatus, locationAlgorithmStatus);
}
@NonNull
+ private static LocationTimeZoneAlgorithmStatus createLocationAlgorithmStatus(
+ ConfigurationInternal currentConfigurationInternal,
+ LocationAlgorithmEvent latestLocationAlgorithmEvent) {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus;
+ if (latestLocationAlgorithmEvent != null) {
+ locationAlgorithmStatus = latestLocationAlgorithmEvent.getAlgorithmStatus();
+ } else if (!currentConfigurationInternal.isGeoDetectionSupported()) {
+ locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.NOT_SUPPORTED;
+ } else if (currentConfigurationInternal.isGeoDetectionExecutionEnabled()) {
+ locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.RUNNING_NOT_REPORTED;
+ } else {
+ locationAlgorithmStatus = LocationTimeZoneAlgorithmStatus.NOT_RUNNING;
+ }
+ return locationAlgorithmStatus;
+ }
+
+ @NonNull
private static TelephonyTimeZoneAlgorithmStatus createTelephonyAlgorithmStatus(
@NonNull ConfigurationInternal currentConfigurationInternal) {
int algorithmStatus;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 3bb0238..14d6d7b 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -226,9 +227,8 @@
mBackAnimationInProgress = true;
// We don't have an application callback, let's find the destination of the back gesture
- Task finalTask = currentTask;
- prevActivity = currentTask.getActivity(
- (r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
+ // The search logic should align with ActivityClientController#finishActivity
+ prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
// TODO Dialog window does not need to attach on activity, check
// window.mAttrs.type != TYPE_BASE_APPLICATION
if ((window.getParent().getChildCount() > 1
@@ -244,12 +244,14 @@
} else if (currentTask.returnsToHomeRootTask()) {
// Our Task should bring back to home
removedWindowContainer = currentTask;
+ prevTask = currentTask.getDisplayArea().getRootHomeTask();
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
mShowWallpaper = true;
} else if (currentActivity.isRootOfTask()) {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
- prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
+ prevTask = currentTask.mRootWindowContainer.getTask(Task::showToCurrentUser,
+ currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
removedWindowContainer = currentTask;
// If it reaches the top activity, we will check the below task from parent.
// If it's null or multi-window, fallback the type to TYPE_CALLBACK.
@@ -423,6 +425,11 @@
void reset(@NonNull WindowContainer close, @NonNull WindowContainer open) {
clearBackAnimateTarget(null);
+ if (close == null || open == null) {
+ Slog.e(TAG, "reset animation with null target close: "
+ + close + " open: " + open);
+ return;
+ }
if (close.asActivityRecord() != null && open.asActivityRecord() != null
&& (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) {
mSwitchType = ACTIVITY_SWITCH;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 374da1c..d3b9e10 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -22,10 +22,11 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialRequest;
-import android.credentials.IClearCredentialSessionCallback;
+import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
@@ -34,6 +35,7 @@
import android.os.ICancellationSignal;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.GetCredentialsRequest;
import android.text.TextUtils;
import android.util.Log;
@@ -198,7 +200,7 @@
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(providerCreateSession -> {
providerCreateSession.getRemoteCredentialService().onCreateCredential(
- (android.service.credentials.CreateCredentialRequest)
+ (BeginCreateCredentialRequest)
providerCreateSession.getProviderRequest(),
/*callback=*/providerCreateSession);
});
@@ -206,8 +208,8 @@
}
@Override
- public ICancellationSignal clearCredentialSession(
- IClearCredentialSessionCallback callback, String callingPackage) {
+ public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
+ IClearCredentialStateCallback callback, String callingPackage) {
// TODO: implement.
Log.i(TAG, "clearCredentialSession");
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index 4cdc457..d0bc074 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -22,7 +22,7 @@
import android.credentials.Credential;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CredentialProviderService;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.CredentialsResponseContent;
/**
* Helper class for setting up pending intent, and extracting objects from it.
@@ -37,14 +37,15 @@
return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
}
- /** Extracts the {@link CredentialsDisplayContent} object added to the result data. */
- public static CredentialsDisplayContent extractCredentialsDisplayContent(Intent resultData) {
+ /** Extracts the {@link CredentialsResponseContent} object added to the result data. */
+ public static CredentialsResponseContent extractResponseContent(Intent resultData) {
if (resultData == null) {
return null;
}
return resultData.getParcelableExtra(
- CredentialProviderService.EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT,
- CredentialsDisplayContent.class);
+ CredentialProviderService
+ .EXTRA_GET_CREDENTIALS_CONTENT_RESULT,
+ CredentialsResponseContent.class);
}
/** Extracts the {@link CreateCredentialResponse} object added to the result data. */
@@ -53,7 +54,7 @@
return null;
}
return resultData.getParcelableExtra(
- CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESPONSE,
+ CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESULT,
CreateCredentialResponse.class);
}
@@ -63,7 +64,7 @@
return null;
}
return resultData.getParcelableExtra(
- CredentialProviderService.EXTRA_GET_CREDENTIAL,
+ CredentialProviderService.EXTRA_CREDENTIAL_RESULT,
Credential.class);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 6bb8c60..332a75e 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -26,11 +26,12 @@
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.Bundle;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CreateCredentialRequest;
-import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.CreateEntry;
import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
-import android.service.credentials.SaveEntry;
import android.util.Log;
import android.util.Slog;
@@ -44,14 +45,14 @@
* Will likely split this into remote response state and UI state.
*/
public final class ProviderCreateSession extends ProviderSession<
- CreateCredentialRequest, CreateCredentialResponse> {
+ BeginCreateCredentialRequest, BeginCreateCredentialResponse> {
private static final String TAG = "ProviderCreateSession";
// Key to be used as an entry key for a save entry
private static final String SAVE_ENTRY_KEY = "save_entry_key";
@NonNull
- private final Map<String, SaveEntry> mUiSaveEntries = new HashMap<>();
+ private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
/** The complete request to be used in the second round. */
private final CreateCredentialRequest mCompleteRequest;
@@ -62,13 +63,19 @@
CredentialProviderInfo providerInfo,
CreateRequestSession createRequestSession,
RemoteCredentialService remoteCredentialService) {
- CreateCredentialRequest providerRequest =
+ CreateCredentialRequest providerCreateRequest =
createProviderRequest(providerInfo.getCapabilities(),
createRequestSession.mClientRequest,
createRequestSession.mClientCallingPackage);
- if (providerRequest != null) {
+ if (providerCreateRequest != null) {
+ // TODO : Replace with proper splitting of request
+ BeginCreateCredentialRequest providerBeginCreateRequest =
+ new BeginCreateCredentialRequest(
+ providerCreateRequest.getCallingPackage(),
+ providerCreateRequest.getType(),
+ new Bundle());
return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
- remoteCredentialService, providerRequest);
+ remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
}
Log.i(TAG, "Unable to create provider session");
return null;
@@ -87,36 +94,28 @@
return null;
}
- private static CreateCredentialRequest getFirstRoundRequest(CreateCredentialRequest request) {
- // TODO: Replace with first round bundle from request when ready
- return new CreateCredentialRequest(
- request.getCallingPackage(),
- request.getType(),
- new Bundle());
- }
-
private ProviderCreateSession(
@NonNull Context context,
@NonNull CredentialProviderInfo info,
@NonNull ProviderInternalCallback callbacks,
@UserIdInt int userId,
@NonNull RemoteCredentialService remoteCredentialService,
- @NonNull CreateCredentialRequest request) {
- super(context, info, getFirstRoundRequest(request), callbacks, userId,
+ @NonNull BeginCreateCredentialRequest beginCreateRequest,
+ @NonNull CreateCredentialRequest completeCreateRequest) {
+ super(context, info, beginCreateRequest, callbacks, userId,
remoteCredentialService);
- // TODO : Replace with proper splitting of request
- mCompleteRequest = request;
+ mCompleteRequest = completeCreateRequest;
setStatus(Status.PENDING);
}
/** Returns the save entry maintained in state by this provider session. */
- public SaveEntry getUiSaveEntry(String entryId) {
+ public CreateEntry getUiSaveEntry(String entryId) {
return mUiSaveEntries.get(entryId);
}
@Override
public void onProviderResponseSuccess(
- @Nullable CreateCredentialResponse response) {
+ @Nullable BeginCreateCredentialResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
onUpdateResponse(response);
}
@@ -138,7 +137,7 @@
}
}
- private void onUpdateResponse(CreateCredentialResponse response) {
+ private void onUpdateResponse(BeginCreateCredentialResponse response) {
Log.i(TAG, "updateResponse with save entries");
mProviderResponse = response;
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
@@ -152,15 +151,15 @@
Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
return null;
}
- final CreateCredentialResponse response = getProviderResponse();
+ final BeginCreateCredentialResponse response = getProviderResponse();
if (response == null) {
Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
}
- if (response.getSaveEntries() != null) {
+ if (response.getCreateEntries() != null) {
Log.i(TAG, "In prepareUiData save entries not null");
return prepareUiProviderData(
- prepareUiSaveEntries(response.getSaveEntries()),
+ prepareUiSaveEntries(response.getCreateEntries()),
null,
/*isDefaultProvider=*/false);
}
@@ -192,24 +191,25 @@
}
}
- private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+ private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
Log.i(TAG, "in populateUiSaveEntries");
List<Entry> uiSaveEntries = new ArrayList<>();
// Populate the save entries
- for (SaveEntry saveEntry : saveEntries) {
+ for (CreateEntry createEntry : saveEntries) {
String entryId = generateEntryId();
- mUiSaveEntries.put(entryId, saveEntry);
+ mUiSaveEntries.put(entryId, createEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
- uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice(),
- saveEntry.getPendingIntent(), setUpFillInIntent(saveEntry.getPendingIntent())));
+ uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, createEntry.getSlice(),
+ createEntry.getPendingIntent(), setUpFillInIntent(
+ createEntry.getPendingIntent())));
}
return uiSaveEntries;
}
private Intent setUpFillInIntent(PendingIntent pendingIntent) {
Intent intent = pendingIntent.getIntent();
- intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
+ intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
mCompleteRequest);
return intent;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index d63cdeb..6cd011b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -29,7 +29,7 @@
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.CredentialsResponseContent;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
import android.util.Log;
@@ -211,20 +211,20 @@
prepareUiAuthenticationAction(mProviderResponse.getAuthenticationAction()),
/*remoteEntry=*/null);
}
- if (mProviderResponse.getCredentialsDisplayContent() != null) {
- Log.i(TAG, "In prepareUiData displayContent not null");
+ if (mProviderResponse.getCredentialsResponseContent() != null) {
+ Log.i(TAG, "In prepareUiData credentialsResponseContent not null");
return prepareUiProviderData(prepareUiActionEntries(
- mProviderResponse.getCredentialsDisplayContent().getActions()),
- prepareUiCredentialEntries(mProviderResponse.getCredentialsDisplayContent()
+ mProviderResponse.getCredentialsResponseContent().getActions()),
+ prepareUiCredentialEntries(mProviderResponse.getCredentialsResponseContent()
.getCredentialEntries()),
/*authenticationAction=*/null,
prepareUiRemoteEntry(mProviderResponse
- .getCredentialsDisplayContent().getRemoteCredentialEntry()));
+ .getCredentialsResponseContent().getRemoteCredentialEntry()));
}
return null;
}
- private Entry prepareUiRemoteEntry(Action remoteCredentialEntry) {
+ private Entry prepareUiRemoteEntry(CredentialEntry remoteCredentialEntry) {
if (remoteCredentialEntry == null) {
return null;
}
@@ -316,11 +316,11 @@
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse != null) {
if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
- CredentialsDisplayContent content = PendingIntentResultHandler
- .extractCredentialsDisplayContent(providerPendingIntentResponse
+ CredentialsResponseContent content = PendingIntentResultHandler
+ .extractResponseContent(providerPendingIntentResponse
.getResultData());
if (content != null) {
- onUpdateResponse(GetCredentialsResponse.createWithDisplayContent(content));
+ onUpdateResponse(GetCredentialsResponse.createWithResponseContent(content));
return;
}
}
@@ -342,7 +342,7 @@
if (response.getAuthenticationAction() != null) {
Log.i(TAG , "updateResponse with authentication entry");
updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
- } else if (response.getCredentialsDisplayContent() != null) {
+ } else if (response.getCredentialsResponseContent() != null) {
Log.i(TAG , "updateResponse with credentialEntries");
// TODO validate response
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 4a07f0a..ac360bd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -23,7 +23,7 @@
import android.credentials.Credential;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
-import android.service.credentials.Action;
+import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
import android.util.Pair;
@@ -50,7 +50,7 @@
@Nullable protected Credential mFinalCredentialResponse;
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
- @Nullable protected Pair<String, Action> mUiRemoteEntry;
+ @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
/**
* Returns true if the given status reflects that the provider state is ready to be shown
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index c2464b5..e385bcb 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,14 +24,14 @@
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.service.credentials.CreateCredentialRequest;
-import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.BeginCreateCredentialRequest;
+import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderException.CredentialProviderError;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
-import android.service.credentials.ICreateCredentialCallback;
+import android.service.credentials.IBeginCreateCredentialCallback;
import android.service.credentials.ICredentialProviderService;
import android.service.credentials.IGetCredentialsCallback;
import android.text.format.DateUtils;
@@ -146,27 +146,27 @@
handleExecutionResponse(result, error, cancellationSink, callback)));
}
- /** Main entry point to be called for executing a createCredential call on the remote
+ /** Main entry point to be called for executing a beginCreateCredential call on the remote
* provider service.
* @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
* {@link ProviderCreateSession} class that maintains provider state
*/
- public void onCreateCredential(@NonNull CreateCredentialRequest request,
- ProviderCallbacks<CreateCredentialResponse> callback) {
+ public void onCreateCredential(@NonNull BeginCreateCredentialRequest request,
+ ProviderCallbacks<BeginCreateCredentialResponse> callback) {
Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
- AtomicReference<CompletableFuture<CreateCredentialResponse>> futureRef =
+ AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
new AtomicReference<>();
- CompletableFuture<CreateCredentialResponse> connectThenExecute = postAsync(service -> {
- CompletableFuture<CreateCredentialResponse> createCredentialFuture =
+ CompletableFuture<BeginCreateCredentialResponse> connectThenExecute = postAsync(service -> {
+ CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
new CompletableFuture<>();
- ICancellationSignal cancellationSignal = service.onCreateCredential(
- request, new ICreateCredentialCallback.Stub() {
+ ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
+ request, new IBeginCreateCredentialCallback.Stub() {
@Override
- public void onSuccess(CreateCredentialResponse response) {
- Log.i(TAG, "In onSuccess onCreateCredential "
+ public void onSuccess(BeginCreateCredentialResponse response) {
+ Log.i(TAG, "In onSuccess onBeginCreateCredential "
+ "in RemoteCredentialService");
createCredentialFuture.complete(response);
}
@@ -179,7 +179,7 @@
createCredentialFuture.completeExceptionally(
new CredentialProviderException(errorCode, errorMsg));
}});
- CompletableFuture<CreateCredentialResponse> future = futureRef.get();
+ CompletableFuture<BeginCreateCredentialResponse> future = futureRef.get();
if (future != null && future.isCancelled()) {
dispatchCancellationSignal(cancellationSignal);
} else {
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 693f3a0..1bd5031 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -788,7 +788,7 @@
private void updateDefaultSmsApp(@NonNull UserData userData) {
ComponentName component = SmsApplication.getDefaultSmsApplicationAsUser(
- mContext, /* updateIfNeeded= */ false, userData.getUserId());
+ mContext, /* updateIfNeeded= */ false, UserHandle.of(userData.getUserId()));
String defaultSmsApp = component != null ? component.getPackageName() : null;
userData.setDefaultSmsApp(defaultSmsApp);
}
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 298cbf3..6af7269 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -74,6 +74,7 @@
import android.app.Application;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupManager;
@@ -183,7 +184,7 @@
private static final String BACKUP_AGENT_SHARED_PREFS_SYNCHRONIZER_CLASS =
"android.app.backup.BackupAgent$SharedPrefsSynchronizer";
private static final int USER_ID = 10;
- private static final int OPERATION_TYPE = BackupManager.OperationType.BACKUP;
+ private static final int BACKUP_DESTINATION = BackupAnnotations.BackupDestination.CLOUD;
@Mock private TransportManager mTransportManager;
@Mock private DataChangedJournal mOldJournal;
@@ -264,7 +265,8 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
mBackupEligibilityRules = new BackupEligibilityRules(mPackageManager,
- LocalServices.getService(PackageManagerInternal.class), USER_ID, OPERATION_TYPE);
+ LocalServices.getService(PackageManagerInternal.class), USER_ID,
+ BACKUP_DESTINATION);
}
@After
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 55d1160..9234431 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -439,14 +439,25 @@
@SuppressWarnings("GuardedBy")
@Test
public void testUpdateOomAdj_DoOne_FgService_ShortFgs() {
+ sService.mConstants.TOP_TO_FGS_GRACE_DURATION = 100_000;
+ sService.mConstants.mShortFgsProcStateExtraWaitDuration = 200_000;
+
+ ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+ s.startRequested = true;
+ s.isForeground = true;
+ s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ s.setShortFgsInfo(SystemClock.uptimeMillis());
+
// SHORT_SERVICE FGS will get IMP_FG and a slightly different recent-adjustment.
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ app.mServices.startService(s);
app.mServices.setHasForegroundServices(true,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
@@ -461,9 +472,11 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setHasForegroundServices(true,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ app.mServices.startService(s);
app.mState.setLastTopTime(SystemClock.uptimeMillis()
- sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
@@ -471,6 +484,33 @@
// Still should get network access.
assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) != 0);
}
+
+ // SHORT_SERVICE, timed out already.
+ s = ServiceRecord.newEmptyInstanceForTest(sService);
+ s.startRequested = true;
+ s.isForeground = true;
+ s.foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ s.setShortFgsInfo(SystemClock.uptimeMillis()
+ - sService.mConstants.mShortFgsTimeoutDuration
+ - sService.mConstants.mShortFgsProcStateExtraWaitDuration);
+ {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ app.mServices.setHasForegroundServices(true,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ app.mServices.startService(s);
+ app.mState.setLastTopTime(SystemClock.uptimeMillis()
+ - sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+ sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ // Procstate should be lower than FGS. (It should be SERVICE)
+ assertEquals(app.mState.getSetProcState(), PROCESS_STATE_SERVICE);
+
+ // Shouldn't have the network capability now.
+ assertTrue((app.mState.getSetCapability() & PROCESS_CAPABILITY_NETWORK) == 0);
+ }
}
@SuppressWarnings("GuardedBy")
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
index ac23d4e..014ef3d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeUserInfoHelper.java
@@ -34,14 +34,16 @@
public static final int DEFAULT_USERID = 0;
private final IntArray mRunningUserIds;
+ private final IntArray mVisibleUserIds;
private final SparseArray<IntArray> mProfiles;
private int mCurrentUserId;
public FakeUserInfoHelper() {
mCurrentUserId = DEFAULT_USERID;
- mRunningUserIds = IntArray.wrap(new int[]{DEFAULT_USERID});
+ mRunningUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
mProfiles = new SparseArray<>();
+ mVisibleUserIds = IntArray.wrap(new int[] {DEFAULT_USERID});
}
public void startUser(int userId) {
@@ -65,6 +67,7 @@
mRunningUserIds.remove(idx);
}
+ setUserInvisibleInternal(userId);
dispatchOnUserStopped(userId);
}
@@ -82,16 +85,39 @@
// ensure all profiles are started if they didn't exist before...
for (int userId : currentProfileUserIds) {
startUserInternal(userId, false);
+ setUserVisibleInternal(userId, true);
}
if (oldUserId != mCurrentUserId) {
dispatchOnCurrentUserChanged(oldUserId, mCurrentUserId);
+ setUserVisibleInternal(mCurrentUserId, true);
}
}
- @Override
- public int[] getRunningUserIds() {
- return mRunningUserIds.toArray();
+ private void setUserVisibleInternal(int userId, boolean alwaysDispatch) {
+ int idx = mVisibleUserIds.indexOf(userId);
+ if (idx < 0) {
+ mVisibleUserIds.add(userId);
+ } else if (!alwaysDispatch) {
+ return;
+ }
+ dispatchOnVisibleUserChanged(userId, true);
+ }
+
+ private void setUserInvisibleInternal(int userId) {
+ int idx = mVisibleUserIds.indexOf(userId);
+ if (idx >= 0) {
+ mVisibleUserIds.remove(userId);
+ }
+ dispatchOnVisibleUserChanged(userId, false);
+ }
+
+ public void setUserVisible(int userId, boolean visible) {
+ if (visible) {
+ setUserVisibleInternal(userId, true);
+ } else {
+ setUserInvisibleInternal(userId);
+ }
}
@Override
@@ -100,11 +126,21 @@
}
@Override
+ public int[] getRunningUserIds() {
+ return mRunningUserIds.toArray();
+ }
+
+ @Override
public int getCurrentUserId() {
return mCurrentUserId;
}
@Override
+ public boolean isVisibleUserId(int userId) {
+ return mVisibleUserIds.indexOf(userId) >= 0;
+ }
+
+ @Override
protected int[] getProfileIds(int userId) {
IntArray profiles = mProfiles.get(userId);
if (profiles != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
index 490b2e8..d9aa232 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemUserInfoHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.location.injector;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -31,6 +32,7 @@
import com.android.server.LocalServices;
import com.android.server.location.injector.UserInfoHelper.UserListener;
+import com.android.server.pm.UserManagerInternal;
import org.junit.After;
import org.junit.Before;
@@ -45,13 +47,14 @@
private static final int USER1_ID = 1;
private static final int USER1_MANAGED_ID = 11;
- private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+ private static final int[] USER1_PROFILES = new int[] {USER1_ID, USER1_MANAGED_ID};
private static final int USER2_ID = 2;
private static final int USER2_MANAGED_ID = 12;
- private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+ private static final int[] USER2_PROFILES = new int[] {USER2_ID, USER2_MANAGED_ID};
@Mock private Context mContext;
@Mock private UserManager mUserManager;
+ @Mock private UserManagerInternal mUserManagerInternal;
private SystemUserInfoHelper mHelper;
@@ -63,12 +66,15 @@
doReturn(USER1_PROFILES).when(mUserManager).getEnabledProfileIds(USER1_ID);
doReturn(USER2_PROFILES).when(mUserManager).getEnabledProfileIds(USER2_ID);
+ LocalServices.addService(UserManagerInternal.class, mUserManagerInternal);
+
mHelper = new SystemUserInfoHelper(mContext);
}
@After
public void tearDown() {
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
}
@Test
@@ -77,11 +83,11 @@
mHelper.addListener(listener);
mHelper.dispatchOnCurrentUserChanged(USER1_ID, USER2_ID);
- verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
- verify(listener, times(1)).onUserChanged(USER1_MANAGED_ID,
+ verify(listener).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED);
+ verify(listener).onUserChanged(USER1_MANAGED_ID,
UserListener.CURRENT_USER_CHANGED);
- verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
- verify(listener, times(1)).onUserChanged(USER2_MANAGED_ID,
+ verify(listener).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED);
+ verify(listener).onUserChanged(USER2_MANAGED_ID,
UserListener.CURRENT_USER_CHANGED);
mHelper.dispatchOnCurrentUserChanged(USER2_ID, USER1_ID);
@@ -94,6 +100,25 @@
}
@Test
+ public void testListener_UserVisibilityChanged() {
+ mHelper.onSystemReady();
+ verify(mUserManagerInternal).addUserVisibilityListener(any());
+
+ UserListener listener = mock(UserListener.class);
+ mHelper.addListener(listener);
+
+ mHelper.dispatchOnVisibleUserChanged(USER1_ID, false);
+ mHelper.dispatchOnVisibleUserChanged(USER2_ID, true);
+ verify(listener).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
+ verify(listener).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
+
+ mHelper.dispatchOnVisibleUserChanged(USER2_ID, false);
+ mHelper.dispatchOnVisibleUserChanged(USER1_ID, true);
+ verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_VISIBILITY_CHANGED);
+ verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_VISIBILITY_CHANGED);
+ }
+
+ @Test
public void testListener_StartUser() {
UserListener listener = mock(UserListener.class);
mHelper.addListener(listener);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 20e4e80..aa28ad4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -175,7 +175,9 @@
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
mInjector = new TestInjector(mContext);
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
mInjector.getUserInfoHelper().startUser(OTHER_USER);
+ mInjector.getUserInfoHelper().setUserVisible(OTHER_USER, true);
mPassive = new PassiveLocationProviderManager(mContext, mInjector);
mPassive.startManager(null);
@@ -331,6 +333,20 @@
}
@Test
+ public void testGetLastLocation_InvisibleUser() {
+ Location loc = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+ assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isNull();
+
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+ assertThat(mManager.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(loc);
+ }
+
+ @Test
public void testGetLastLocation_Bypass() {
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
new PackageTagsList.Builder().add(
@@ -569,6 +585,25 @@
}
@Test
+ public void testRegisterListener_InvisibleUser() throws Exception {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = new LocationRequest.Builder(0)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+ mProvider.setProviderLocation(createLocationResult(NAME, mRandom));
+ verify(listener, never()).onLocationChanged(any(List.class),
+ nullable(IRemoteCallback.class));
+
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+ LocationResult loc = createLocationResult(NAME, mRandom);
+ mProvider.setProviderLocation(loc);
+ verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
+ }
+
+ @Test
public void testRegisterListener_ExpiringAlarm() throws Exception {
ILocationListener listener = createMockLocationListener();
LocationRequest request = new LocationRequest.Builder(0)
@@ -799,6 +834,17 @@
}
@Test
+ public void testGetCurrentLocation_InvisibleUser() throws Exception {
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+
+ ILocationCallback listener = createMockGetCurrentLocationListener();
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+ mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
+
+ verify(listener).onLocation(isNull());
+ }
+
+ @Test
public void testFlush() throws Exception {
ILocationListener listener = createMockLocationListener();
mManager.registerLocationRequest(
@@ -1008,6 +1054,21 @@
}
@Test
+ public void testProviderRequest_InvisibleUser() {
+ ILocationListener listener = createMockLocationListener();
+ LocationRequest request = new LocationRequest.Builder(5)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, false);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+
+ mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);
+ assertThat(mProvider.getRequest().isActive()).isTrue();
+ }
+
+ @Test
public void testProviderRequest_IgnoreLocationSettings() {
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
new PackageTagsList.Builder().add(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
index afcedd6..a97491d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
@@ -40,7 +40,6 @@
private static final String TAG = AsyncUserVisibilityListener.class.getSimpleName();
private static final long WAIT_TIMEOUT_MS = 2_000;
-
private static final long WAIT_NO_EVENTS_TIMEOUT_MS = 100;
private static int sNextId;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index c5a8572..6d8910e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -15,12 +15,14 @@
*/
package com.android.server.pm;
+import static android.os.UserHandle.USER_NULL;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
@@ -40,6 +42,88 @@
}
@Test
+ public void testStartFgUser_onDefaultDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(USER_ID));
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(USER_ID);
+ expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+ listener.verify();
+ }
+
+ @Test
+ public void testSwitchFgUser_onDefaultDisplay() throws Exception {
+ int previousCurrentUserId = OTHER_USER_ID;
+ int currentUserId = USER_ID;
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(previousCurrentUserId),
+ onInvisible(previousCurrentUserId),
+ onVisible(currentUserId));
+ startForegroundUser(previousCurrentUserId);
+
+ int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(currentUserId);
+ expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(currentUserId);
+
+ expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+ expectUserIsNotVisibleAtAll(previousCurrentUserId);
+ expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+ listener.verify();
+ }
+
+ @Test
+ public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID));
+ startForegroundUser(PARENT_USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(PROFILE_USER_ID);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+ expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
public void testStartFgUser_onInvalidDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
@@ -89,15 +173,15 @@
startDefaultProfile();
// Make sure they were visible before
- expectUserIsVisibleOnDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
- expectUserIsNotVisibleOnDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsNotVisibleOnDisplay("after", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index fc0287f..1065392 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -15,7 +15,15 @@
*/
package com.android.server.pm;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
import org.junit.Test;
@@ -33,6 +41,88 @@
}
@Test
+ public void testStartFgUser_onDefaultDisplay() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(USER_ID));
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(USER_ID);
+ expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+ expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+ expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(USER_ID);
+
+ expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+ expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+ listener.verify();
+ }
+
+ @Test
+ public void testSwitchFgUser_onDefaultDisplay() throws Exception {
+ int previousCurrentUserId = OTHER_USER_ID;
+ int currentUserId = USER_ID;
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(previousCurrentUserId),
+ onInvisible(previousCurrentUserId),
+ onVisible(currentUserId));
+ startForegroundUser(previousCurrentUserId);
+
+ int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(currentUserId);
+ expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+ expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+ expectUserIsVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(currentUserId);
+
+ expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+ expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+ expectUserIsNotVisibleAtAll(previousCurrentUserId);
+ expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+ listener.verify();
+ }
+
+ @Test
+ public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID));
+ startForegroundUser(PARENT_USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ expectUserIsVisible(PROFILE_USER_ID);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+ expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
public void testStartBgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 6ceb38a..c203831 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -37,6 +37,7 @@
import android.annotation.UserIdInt;
import android.os.Handler;
+import android.text.TextUtils;
import android.util.IntArray;
import android.util.Log;
@@ -135,66 +136,6 @@
}
@Test
- public final void testStartFgUser_onDefaultDisplay() throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(
- onInvisible(INITIAL_CURRENT_USER_ID),
- onVisible(USER_ID));
-
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
- DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsVisible(USER_ID);
- expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
- expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
- // TODO(b/244644281): once isUserVisible() is fixed (see note there), this assertion will
- // fail on MUMD, so we'll need to refactor / split this test (and possibly others)
- expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- expectVisibleUsers(USER_ID);
-
- expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
- expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
- expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
- expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
-
- expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
-
- listener.verify();
- }
-
- @Test
- public final void testSwitchFgUser_onDefaultDisplay() throws Exception {
- int previousCurrentUserId = OTHER_USER_ID;
- int currentUserId = USER_ID;
- AsyncUserVisibilityListener listener = addListenerForEvents(
- onInvisible(INITIAL_CURRENT_USER_ID),
- onVisible(previousCurrentUserId),
- onInvisible(previousCurrentUserId),
- onVisible(currentUserId));
- startForegroundUser(previousCurrentUserId);
-
- int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
- DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsVisible(currentUserId);
- expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
- expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
- expectUserIsVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
- expectVisibleUsers(currentUserId);
-
- expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
- expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
- expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
- expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
-
- expectUserIsNotVisibleAtAll(previousCurrentUserId);
- expectNoDisplayAssignedToUser(previousCurrentUserId);
-
- listener.verify();
- }
-
- @Test
public final void testStartFgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
@@ -245,31 +186,6 @@
}
@Test
- public final void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
- throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(
- onInvisible(INITIAL_CURRENT_USER_ID),
- onVisible(PARENT_USER_ID),
- onVisible(PROFILE_USER_ID));
- startForegroundUser(PARENT_USER_ID);
-
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
- DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
- expectUserIsVisible(PROFILE_USER_ID);
- expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
- expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
- expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
- expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
-
- expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
- expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
-
- listener.verify();
- }
-
- @Test
public final void testStopVisibleProfile() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
@@ -530,6 +446,14 @@
.isFalse();
}
+ protected void expectUserIsNotVisibleOnDisplay(String when, @UserIdInt int userId,
+ int displayId) {
+ String suffix = TextUtils.isEmpty(when) ? "" : " on " + when;
+ expectWithMessage("mediator.isUserVisible(%s, %s)%s", userId, displayId, suffix)
+ .that(mMediator.isUserVisible(userId, displayId))
+ .isFalse();
+ }
+
protected void expectUserIsNotVisibleAtAll(@UserIdInt int userId) {
expectWithMessage("mediator.isUserVisible(%s)", userId)
.that(mMediator.isUserVisible(userId))
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 0f09252..52a550b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -58,7 +58,6 @@
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityWindowAttributes;
import androidx.test.InstrumentationRegistry;
@@ -106,8 +105,6 @@
LABEL,
DESCRIPTION,
TEST_PENDING_INTENT);
- private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
- new AccessibilityAction(ACTION_ID, LABEL);
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
@@ -282,10 +279,12 @@
@Test
public void testRegisterProxy() throws Exception {
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
- verify(mProxyManager).registerProxy(mMockServiceClient, TEST_DISPLAY);
+ verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
+ eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
+ eq(mA11yms), eq(mA11yms.getTraceManager()),
+ eq(mMockWindowManagerService), eq(mMockA11yWindowManager));
}
-
@SmallTest
@Test
public void testRegisterProxyWithoutPermission() throws Exception {
@@ -296,7 +295,8 @@
Assert.fail();
} catch (SecurityException expected) {
}
- verify(mProxyManager, never()).registerProxy(mMockServiceClient, TEST_DISPLAY);
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ any(), any(), any(), any());
}
@SmallTest
@@ -307,7 +307,8 @@
Assert.fail();
} catch (IllegalArgumentException expected) {
}
- verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.DEFAULT_DISPLAY);
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ any(), any(), any(), any());
}
@SmallTest
@@ -318,7 +319,30 @@
Assert.fail();
} catch (IllegalArgumentException expected) {
}
- verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.INVALID_DISPLAY);
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ any(), any(), any(), any());
+ }
+
+ @SmallTest
+ @Test
+ public void testUnRegisterProxyWithPermission() throws Exception {
+ mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
+ mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+
+ verify(mProxyManager).unregisterProxy(TEST_DISPLAY);
+ }
+
+ @SmallTest
+ @Test
+ public void testUnRegisterProxyWithoutPermission() throws Exception {
+ doThrow(SecurityException.class).when(mMockSecurityPolicy)
+ .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+ try {
+ mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
+ Assert.fail();
+ } catch (SecurityException expected) {
+ }
+ verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY);
}
@SmallTest
@@ -417,6 +441,8 @@
@SmallTest
@Test
public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
userState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME);
@@ -432,6 +458,8 @@
@SmallTest
@Test
public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
setupAccessibilityServiceConnection(0);
when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 6016558..cd2f205 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -29,7 +29,7 @@
import static org.mockito.Mockito.when;
import android.app.backup.BackupAgent;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.content.Context;
@@ -148,18 +148,18 @@
}
@Test
- public void testGetOperationTypeFromTransport_returnsBackupByDefault()
+ public void testGetBackupDestinationFromTransport_returnsCloudByDefault()
throws Exception {
when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport);
when(mBackupTransport.getTransportFlags()).thenReturn(0);
- int operationType = mService.getOperationTypeFromTransport(mTransportConnection);
+ int backupDestination = mService.getBackupDestinationFromTransport(mTransportConnection);
- assertThat(operationType).isEqualTo(OperationType.BACKUP);
+ assertThat(backupDestination).isEqualTo(BackupDestination.CLOUD);
}
@Test
- public void testGetOperationTypeFromTransport_returnsMigrationForMigrationTransport()
+ public void testGetBackupDestinationFromTransport_returnsDeviceTransferForD2dTransport()
throws Exception {
// This is a temporary flag to control the new behaviour until it's ready to be fully
// rolled out.
@@ -169,9 +169,9 @@
when(mBackupTransport.getTransportFlags()).thenReturn(
BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER);
- int operationType = mService.getOperationTypeFromTransport(mTransportConnection);
+ int backupDestination = mService.getBackupDestinationFromTransport(mTransportConnection);
- assertThat(operationType).isEqualTo(OperationType.MIGRATION);
+ assertThat(backupDestination).isEqualTo(BackupDestination.DEVICE_TRANSFER);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
index 310c8f4..48b0aad 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
@@ -23,7 +23,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
-import android.app.backup.BackupManager.OperationType;
+import android.app.backup.BackupAnnotations.BackupDestination;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -78,7 +78,7 @@
MockitoAnnotations.initMocks(this);
mUserId = UserHandle.USER_SYSTEM;
- mBackupEligibilityRules = getBackupEligibilityRules(OperationType.BACKUP);
+ mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);
}
@Test
@@ -225,7 +225,7 @@
/* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
- OperationType.MIGRATION);
+ BackupDestination.DEVICE_TRANSFER);
boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
assertThat(isEligible).isTrue();
@@ -237,7 +237,7 @@
ApplicationInfo applicationInfo = getApplicationInfo(Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, CUSTOM_BACKUP_AGENT_NAME);
BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
- OperationType.MIGRATION);
+ BackupDestination.DEVICE_TRANSFER);
boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
assertThat(isEligible).isFalse();
@@ -250,7 +250,7 @@
ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
/* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME);
BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
- OperationType.ADB_BACKUP);
+ BackupDestination.ADB_BACKUP);
when(mPackageManager.getPropertyAsUser(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP),
eq(TEST_PACKAGE_NAME), isNull(), eq(mUserId)))
.thenReturn(getAdbBackupProperty(/* allowAdbBackup */ false));
@@ -267,7 +267,7 @@
ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
/* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME);
BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
- OperationType.ADB_BACKUP);
+ BackupDestination.ADB_BACKUP);
when(mPackageManager.getPropertyAsUser(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP),
eq(TEST_PACKAGE_NAME), isNull(), eq(mUserId)))
.thenReturn(getAdbBackupProperty(/* allowAdbBackup */ true));
@@ -284,7 +284,7 @@
ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
/* flags */ ApplicationInfo.FLAG_DEBUGGABLE, CUSTOM_BACKUP_AGENT_NAME);
BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
- OperationType.ADB_BACKUP);
+ BackupDestination.ADB_BACKUP);
boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
@@ -298,7 +298,7 @@
ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
ApplicationInfo.FLAG_ALLOW_BACKUP, CUSTOM_BACKUP_AGENT_NAME);
BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
- OperationType.ADB_BACKUP);
+ BackupDestination.ADB_BACKUP);
boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
@@ -312,7 +312,7 @@
ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
/* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
BackupEligibilityRules eligibilityRules = getBackupEligibilityRules(
- OperationType.ADB_BACKUP);
+ BackupDestination.ADB_BACKUP);
boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo);
@@ -787,9 +787,10 @@
assertThat(result).isFalse();
}
- private BackupEligibilityRules getBackupEligibilityRules(@OperationType int operationType) {
+ private BackupEligibilityRules getBackupEligibilityRules(
+ @BackupDestination int backupDestination) {
return new BackupEligibilityRules(mPackageManager, mMockPackageManagerInternal, mUserId,
- operationType);
+ backupDestination);
}
private static Signature generateSignature(byte i) {
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index ccc43f2..a5d7a10 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -144,6 +144,7 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
Display.STATE_ON), mInvalidBrightnessStrategy);
}
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 6590a2b..ecd9d89 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -16,6 +16,7 @@
package com.android.server.input
+import android.bluetooth.BluetoothDevice
import android.content.Context
import android.content.ContextWrapper
import android.hardware.BatteryState.STATUS_CHARGING
@@ -33,6 +34,8 @@
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
import androidx.test.InstrumentationRegistry
+import com.android.server.input.BatteryController.BluetoothBatteryManager
+import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener
import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
import com.android.server.input.BatteryController.UEventManager
import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
@@ -52,6 +55,7 @@
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.notNull
import org.mockito.Mock
import org.mockito.Mockito.anyInt
@@ -172,6 +176,8 @@
const val SECOND_DEVICE_ID = 11
const val USI_DEVICE_ID = 101
const val SECOND_USI_DEVICE_ID = 102
+ const val BT_DEVICE_ID = 100001
+ const val SECOND_BT_DEVICE_ID = 100002
const val TIMESTAMP = 123456789L
}
@@ -184,6 +190,8 @@
private lateinit var iInputManager: IInputManager
@Mock
private lateinit var uEventManager: UEventManager
+ @Mock
+ private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
private lateinit var batteryController: BatteryController
private lateinit var context: Context
@@ -203,11 +211,13 @@
addInputDevice(DEVICE_ID)
addInputDevice(SECOND_DEVICE_ID)
- batteryController = BatteryController(context, native, testLooper.looper, uEventManager)
+ batteryController = BatteryController(context, native, testLooper.looper, uEventManager,
+ bluetoothBatteryManager)
batteryController.systemRunning()
val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java)
verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture())
devicesChangedListener = listenerCaptor.value
+ testLooper.dispatchAll()
}
private fun notifyDeviceChanged(
@@ -230,7 +240,7 @@
private fun addInputDevice(
deviceId: Int,
hasBattery: Boolean = true,
- supportsUsi: Boolean = false
+ supportsUsi: Boolean = false,
) {
deviceGenerationMap[deviceId] = 0
notifyDeviceChanged(deviceId, hasBattery, supportsUsi)
@@ -634,4 +644,125 @@
assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
}
+
+ @Test
+ fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() {
+ `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ .thenReturn("AA:BB:CC:DD:EE:FF")
+ `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+ .thenReturn("11:22:33:44:55:66")
+ addInputDevice(BT_DEVICE_ID)
+ testLooper.dispatchNext()
+ addInputDevice(SECOND_BT_DEVICE_ID)
+ testLooper.dispatchNext()
+
+ // Ensure that a BT battery listener is not added when there are no monitored BT devices.
+ verify(bluetoothBatteryManager, never()).addListener(any())
+
+ val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+ val listener = createMockListener()
+
+ // The BT battery listener is added when the first BT input device is monitored.
+ batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+ verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+
+ // The BT listener is only added once for all BT devices.
+ batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID)
+ verify(bluetoothBatteryManager, times(1)).addListener(any())
+
+ // The BT listener is only removed when there are no monitored BT devices.
+ batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
+ verify(bluetoothBatteryManager, never()).removeListener(any())
+
+ `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+ .thenReturn(null)
+ notifyDeviceChanged(SECOND_BT_DEVICE_ID)
+ testLooper.dispatchNext()
+ verify(bluetoothBatteryManager).removeListener(bluetoothListener.value)
+ }
+
+ @Test
+ fun testNotifiesBluetoothBatteryChanges() {
+ `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ .thenReturn("AA:BB:CC:DD:EE:FF")
+ `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+ addInputDevice(BT_DEVICE_ID)
+ val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+ verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+ listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+ // When the state has not changed, the listener is not notified again.
+ bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+ listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+
+ `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(25)
+ bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+ listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f)
+ }
+
+ @Test
+ fun testBluetoothBatteryIsPrioritized() {
+ `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+ `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ .thenReturn("AA:BB:CC:DD:EE:FF")
+ `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+ `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+ addInputDevice(BT_DEVICE_ID)
+ val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+ val listener = createMockListener()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+ // When the device is first monitored and both native and BT battery is available,
+ // the latter is used.
+ batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+ verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+ verify(uEventManager).addListener(uEventListener.capture(), any())
+ listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+ assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+ matchesState(BT_DEVICE_ID, capacity = 0.21f))
+
+ // If only the native battery state changes the listener is not notified.
+ `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(97)
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+ listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+ assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+ matchesState(BT_DEVICE_ID, capacity = 0.21f))
+ }
+
+ @Test
+ fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() {
+ `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+ `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ .thenReturn("AA:BB:CC:DD:EE:FF")
+ `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+ `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+ addInputDevice(BT_DEVICE_ID)
+ val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+ val listener = createMockListener()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+ batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+ verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+ verify(uEventManager).addListener(uEventListener.capture(), any())
+ listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+ // Fall back to the native state when BT is off.
+ `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF")))
+ .thenReturn(BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF)
+ bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+ listener.verifyNotified(BT_DEVICE_ID, capacity = 0.98f)
+
+ `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(22)
+ bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+ verify(bluetoothBatteryManager).addListener(bluetoothListener.capture())
+ listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f)
+
+ // Fall back to the native state when BT battery is unknown.
+ `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF")))
+ .thenReturn(BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF")
+ listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.98f)
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index b991c5a..74efdb5 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -880,7 +880,7 @@
TimeZoneDetectorStatus expectedInitialDetectorStatus = new TimeZoneDetectorStatus(
DETECTOR_STATUS_RUNNING,
TELEPHONY_ALGORITHM_RUNNING_STATUS,
- LocationTimeZoneAlgorithmStatus.UNKNOWN);
+ LocationTimeZoneAlgorithmStatus.RUNNING_NOT_REPORTED);
script.verifyCachedDetectorStatus(expectedInitialDetectorStatus);
LocationTimeZoneAlgorithmStatus algorithmStatus1 = new LocationTimeZoneAlgorithmStatus(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index afec085..d54d1fe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1101,10 +1101,6 @@
new NotificationChannel("id", "name", IMPORTANCE_HIGH);
mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
- // pretend only this following part is called by the app (system permissions are required to
- // update the notification channel on behalf of the user above)
- mService.isSystemUid = false;
-
// Recreating with a lower importance leaves channel unchanged.
final NotificationChannel dupeChannel =
new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW);
@@ -1130,46 +1126,6 @@
}
@Test
- public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception {
- // Confirm that when createNotificationChannels is called from the relevant app and not
- // system, then it cannot set fields that can't be set by apps
- mService.isSystemUid = false;
-
- final NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
- channel.setBypassDnd(true);
- channel.setAllowBubbles(true);
-
- mBinderService.createNotificationChannels(PKG,
- new ParceledListSlice(Arrays.asList(channel)));
-
- final NotificationChannel createdChannel =
- mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
- assertFalse(createdChannel.canBypassDnd());
- assertFalse(createdChannel.canBubble());
- }
-
- @Test
- public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception {
- // Confirm that when createNotificationChannels is called from system,
- // then it can set fields that can't be set by apps
- mService.isSystemUid = true;
-
- final NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
- channel.setBypassDnd(true);
- channel.setAllowBubbles(true);
-
- mBinderService.createNotificationChannels(PKG,
- new ParceledListSlice(Arrays.asList(channel)));
-
- final NotificationChannel createdChannel =
- mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
- assertTrue(createdChannel.canBypassDnd());
- assertTrue(createdChannel.canBubble());
- }
-
- @Test
public void testBlockedNotifications_suspended() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
@@ -3132,8 +3088,6 @@
@Test
public void testDeleteChannelGroupChecksForFgses() throws Exception {
- // the setup for this test requires it to seem like it's coming from the app
- mService.isSystemUid = false;
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
.thenReturn(singletonList(mock(AssociationInfo.class)));
CountDownLatch latch = new CountDownLatch(2);
@@ -3146,7 +3100,7 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannels(PKG, pls);
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -3165,10 +3119,8 @@
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- // Because existing channels won't have their groups overwritten when the call
- // is from the app, this call won't take the channel out of the group
- mBinderService.createNotificationChannels(PKG, pls);
- mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -8729,7 +8681,7 @@
assertEquals("friend", friendChannel.getConversationId());
assertEquals(null, original.getConversationId());
assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
- assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system
+ assertFalse(friendChannel.canBubble()); // can't be modified by app
assertFalse(original.getId().equals(friendChannel.getId()));
assertNotNull(friendChannel.getId());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index f3f56e0..dc3515d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -114,6 +114,20 @@
}
@Test
+ public void backTypeBackToHomeDifferentUser() {
+ Task taskA = createTask(mDefaultDisplay);
+ ActivityRecord recordA = createActivityRecord(taskA);
+ Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+ doReturn(false).when(taskA).showToCurrentUser();
+
+ withSystemCallback(createTopTaskWithActivity());
+ BackNavigationInfo backNavigationInfo = startBackNavigation();
+ assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+ }
+
+ @Test
public void backTypeCrossActivityWhenBackToPreviousActivity() {
CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index f848c40..a9cdf7e 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -210,6 +210,15 @@
}
/**
+ * Returns the userHandle of the current process, if called from a system app,
+ * otherwise it returns the caller's userHandle
+ * @return userHandle of the caller.
+ */
+ private static UserHandle getIncomingUserHandle() {
+ return UserHandle.of(getIncomingUserId());
+ }
+
+ /**
* Returns the list of available SMS apps defined as apps that are registered for both the
* SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
* receivers are enabled)
@@ -951,24 +960,28 @@
*/
@UnsupportedAppUsage
public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
- return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+ return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
}
/**
* Gets the default SMS application on a given user
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
- * @param userId target user ID.
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
* @return component name of the app and class to deliver SMS messages to
*/
- @VisibleForTesting
public static ComponentName getDefaultSmsApplicationAsUser(Context context,
- boolean updateIfNeeded, int userId) {
+ boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+ if (userHandle == null) {
+ userHandle = getIncomingUserHandle();
+ }
+
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
- userId);
+ userHandle.getIdentifier());
if (smsApplicationData != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mSmsReceiverClass);
@@ -987,23 +1000,28 @@
*/
@UnsupportedAppUsage
public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
- return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+ return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
}
/**
* Gets the default MMS application on a given user
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured.
- * @param userId target user ID.
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
* @return component name of the app and class to deliver MMS messages to.
*/
public static ComponentName getDefaultMmsApplicationAsUser(Context context,
- boolean updateIfNeeded, int userId) {
+ boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+ if (userHandle == null) {
+ userHandle = getIncomingUserHandle();
+ }
+
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
- userId);
+ userHandle.getIdentifier());
if (smsApplicationData != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mMmsReceiverClass);
@@ -1024,23 +1042,28 @@
public static ComponentName getDefaultRespondViaMessageApplication(Context context,
boolean updateIfNeeded) {
return getDefaultRespondViaMessageApplicationAsUser(context, updateIfNeeded,
- getIncomingUserId());
+ getIncomingUserHandle());
}
/**
* Gets the default Respond Via Message application on a given user
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured
- * @param userId target user ID.
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
* @return component name of the app and class to direct Respond Via Message intent to
*/
public static ComponentName getDefaultRespondViaMessageApplicationAsUser(Context context,
- boolean updateIfNeeded, int userId) {
+ boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+ if (userHandle == null) {
+ userHandle = getIncomingUserHandle();
+ }
+
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
- userId);
+ userHandle.getIdentifier());
if (smsApplicationData != null) {
component = new ComponentName(smsApplicationData.mPackageName,
smsApplicationData.mRespondViaMessageClass);
@@ -1062,6 +1085,7 @@
public static ComponentName getDefaultSendToApplication(Context context,
boolean updateIfNeeded) {
int userId = getIncomingUserId();
+
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
@@ -1087,7 +1111,7 @@
public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
Context context, boolean updateIfNeeded) {
return getDefaultExternalTelephonyProviderChangedApplicationAsUser(context, updateIfNeeded,
- getIncomingUserId());
+ getIncomingUserHandle());
}
/**
@@ -1095,16 +1119,21 @@
* MmsProvider on a given user.
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured
- * @param userId target user ID.
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
* @return component name of the app and class to deliver change intents to.
*/
public static ComponentName getDefaultExternalTelephonyProviderChangedApplicationAsUser(
- Context context, boolean updateIfNeeded, int userId) {
+ Context context, boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+ if (userHandle == null) {
+ userHandle = getIncomingUserHandle();
+ }
+
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
- userId);
+ userHandle.getIdentifier());
if (smsApplicationData != null
&& smsApplicationData.mProviderChangedReceiverClass != null) {
component = new ComponentName(smsApplicationData.mPackageName,
@@ -1124,23 +1153,28 @@
*/
public static ComponentName getDefaultSimFullApplication(
Context context, boolean updateIfNeeded) {
- return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserId());
+ return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle());
}
/**
* Gets the default application that handles sim full event on a given user.
* @param context context from the calling app
* @param updateIfNeeded update the default app if there is no valid default app configured
- * @param userId target user ID.
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
* @return component name of the app and class to deliver change intents to
*/
public static ComponentName getDefaultSimFullApplicationAsUser(Context context,
- boolean updateIfNeeded, int userId) {
+ boolean updateIfNeeded, @Nullable UserHandle userHandle) {
+ if (userHandle == null) {
+ userHandle = getIncomingUserHandle();
+ }
+
final long token = Binder.clearCallingIdentity();
try {
ComponentName component = null;
SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
- userId);
+ userHandle.getIdentifier());
if (smsApplicationData != null
&& smsApplicationData.mSimFullReceiverClass != null) {
component = new ComponentName(smsApplicationData.mPackageName,
@@ -1153,19 +1187,35 @@
}
/**
- * Returns whether need to wrgetIncomingUserIdite the SMS message to SMS database for this
- * package.
+ * Returns whether it is required to write the SMS message to SMS database for this package.
+ *
+ * @param packageName the name of the package to be checked
+ * @param context context from the calling app
+ * @return true if it is required to write SMS message to SMS database for this package.
+ *
* <p>
* Caller must pass in the correct user context if calling from a singleton service.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
- return !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserId());
+ return !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserHandle());
}
+ /**
+ * Returns whether it is required to write the SMS message to SMS database for this package.
+ *
+ * @param packageName the name of the package to be checked
+ * @param context context from the calling app
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
+ * @return true if it is required to write SMS message to SMS database for this package.
+ *
+ * <p>
+ * Caller must pass in the correct user context if calling from a singleton service.
+ */
public static boolean shouldWriteMessageForPackageAsUser(String packageName, Context context,
- int userId) {
- return !isDefaultSmsApplicationAsUser(context, packageName, userId);
+ @Nullable UserHandle userHandle) {
+ return !isDefaultSmsApplicationAsUser(context, packageName, userHandle);
}
/**
@@ -1177,7 +1227,7 @@
*/
@UnsupportedAppUsage
public static boolean isDefaultSmsApplication(Context context, String packageName) {
- return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserId());
+ return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserHandle());
}
/**
@@ -1185,16 +1235,22 @@
*
* @param context context from the calling app
* @param packageName the name of the package to be checked
- * @param userId target user ID.
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
* @return true if the package is default sms app or bluetooth
*/
public static boolean isDefaultSmsApplicationAsUser(Context context, String packageName,
- int userId) {
+ @Nullable UserHandle userHandle) {
if (packageName == null) {
return false;
}
+
+ if (userHandle == null) {
+ userHandle = getIncomingUserHandle();
+ }
+
ComponentName component = getDefaultSmsApplicationAsUser(context, false,
- userId);
+ userHandle);
if (component == null) {
return false;
}
@@ -1222,7 +1278,7 @@
*/
@UnsupportedAppUsage
public static boolean isDefaultMmsApplication(Context context, String packageName) {
- return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserId());
+ return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserHandle());
}
/**
@@ -1230,17 +1286,22 @@
*
* @param context context from the calling app
* @param packageName the name of the package to be checked
- * @param userId target user ID.
+ * @param userHandle target user handle
+ * if {@code null} is passed in then calling package uid is used to find out target user handle.
* @return true if the package is default mms app or bluetooth
*/
public static boolean isDefaultMmsApplicationAsUser(Context context, String packageName,
- int userId) {
+ @Nullable UserHandle userHandle) {
if (packageName == null) {
return false;
}
+ if (userHandle == null) {
+ userHandle = getIncomingUserHandle();
+ }
+
ComponentName component = getDefaultMmsApplicationAsUser(context, false,
- userId);
+ userHandle);
if (component == null) {
return false;
}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 76d2b7d..3dc7111 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -27,6 +27,8 @@
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import java.io.PrintWriter;
@@ -212,4 +214,30 @@
return "UNKNOWN(" + mobileDataPolicy + ")";
}
}
-}
+
+ /**
+ * Utility method to get user handle associated with this subscription.
+ *
+ * This method should be used internally as it returns null instead of throwing
+ * IllegalArgumentException or IllegalStateException.
+ *
+ * @param context Context object
+ * @param subId the subId of the subscription.
+ * @return userHandle associated with this subscription
+ * or {@code null} if:
+ * 1. subscription is not associated with any user
+ * 2. subId is invalid.
+ * 3. subscription service is not available.
+ *
+ * @throws SecurityException if the caller doesn't have permissions required.
+ */
+ @Nullable
+ public static UserHandle getSubscriptionUserHandle(Context context, int subId) {
+ UserHandle userHandle = null;
+ SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+ if ((subManager != null) && (SubscriptionManager.isValidSubscriptionId(subId))) {
+ userHandle = subManager.getSubscriptionUserHandle(subId);
+ }
+ return userHandle;
+ }
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 2435243..86b98f1 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -5,7 +5,6 @@
import android.net.NetworkCapabilities;
import android.telecom.Connection;
import android.telephony.data.ApnSetting;
-import android.telephony.ims.ImsCallProfile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -495,7 +494,7 @@
PreciseCallState.PRECISE_CALL_STATE_HOLDING,
PreciseCallState.PRECISE_CALL_STATE_DIALING,
PreciseCallState.PRECISE_CALL_STATE_ALERTING,
- PreciseCallState.PRECISE_CALL_STATE_INCOMING,
+ PreciseCallState. PRECISE_CALL_STATE_INCOMING,
PreciseCallState.PRECISE_CALL_STATE_WAITING,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING})
@@ -728,36 +727,6 @@
})
public @interface ValidationStatus {}
- /**
- * IMS call Service types
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "SERVICE_TYPE_" }, value = {
- ImsCallProfile.SERVICE_TYPE_NONE,
- ImsCallProfile.SERVICE_TYPE_NORMAL,
- ImsCallProfile.SERVICE_TYPE_EMERGENCY,
- })
- public @interface ImsCallServiceType {}
-
- /**
- * IMS call types
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "CALL_TYPE_" }, value = {
- ImsCallProfile.CALL_TYPE_NONE,
- ImsCallProfile.CALL_TYPE_VOICE_N_VIDEO,
- ImsCallProfile.CALL_TYPE_VOICE,
- ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE,
- ImsCallProfile.CALL_TYPE_VT,
- ImsCallProfile.CALL_TYPE_VT_TX,
- ImsCallProfile.CALL_TYPE_VT_RX,
- ImsCallProfile.CALL_TYPE_VT_NODIR,
- ImsCallProfile.CALL_TYPE_VS,
- ImsCallProfile.CALL_TYPE_VS_TX,
- ImsCallProfile.CALL_TYPE_VS_RX,
- })
- public @interface ImsCallType {}
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 1dc64a9..b7bef39 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -29,10 +29,8 @@
* Contains information about a call's attributes as passed up from the HAL. If there are multiple
* ongoing calls, the CallAttributes will pertain to the call in the foreground.
* @hide
- * @deprecated use {@link CallState} for call information for each call.
*/
@SystemApi
-@Deprecated
public final class CallAttributes implements Parcelable {
private PreciseCallState mPreciseCallState;
@NetworkType
diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java
deleted file mode 100644
index 0a267cf..0000000
--- a/telephony/java/android/telephony/CallState.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.telephony.Annotation.ImsCallServiceType;
-import android.telephony.Annotation.ImsCallType;
-import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseCallStates;
-import android.telephony.ims.ImsCallProfile;
-import android.telephony.ims.ImsCallSession;
-
-import java.util.Objects;
-
-/**
- * Contains information about various states for a call.
- * @hide
- */
-@SystemApi
-public final class CallState implements Parcelable {
-
- /**
- * Call classifications are just used for backward compatibility of deprecated API {@link
- * TelephonyCallback#CallAttributesListener#onCallAttributesChanged}, Since these will be
- * removed when the deprecated API is removed, they should not be opened.
- */
- /**
- * Call classification is not valid. It should not be opened.
- * @hide
- */
- public static final int CALL_CLASSIFICATION_UNKNOWN = -1;
-
- /**
- * Call classification indicating foreground call
- * @hide
- */
- public static final int CALL_CLASSIFICATION_RINGING = 0;
-
- /**
- * Call classification indicating background call
- * @hide
- */
- public static final int CALL_CLASSIFICATION_FOREGROUND = 1;
-
- /**
- * Call classification indicating ringing call
- * @hide
- */
- public static final int CALL_CLASSIFICATION_BACKGROUND = 2;
-
- /**
- * Call classification Max value.
- * @hide
- */
- public static final int CALL_CLASSIFICATION_MAX = CALL_CLASSIFICATION_BACKGROUND + 1;
-
- @PreciseCallStates
- private final int mPreciseCallState;
-
- @NetworkType
- private final int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints
- private final CallQuality mCallQuality;
-
- private final int mCallClassification;
- /**
- * IMS call session ID. {@link ImsCallSession#getCallId()}
- */
- @Nullable
- private String mImsCallId;
-
- /**
- * IMS call service type of this call
- */
- @ImsCallServiceType
- private int mImsCallServiceType;
-
- /**
- * IMS call type of this call.
- */
- @ImsCallType
- private int mImsCallType;
-
- /**
- * Constructor of CallAttributes
- *
- * @param callState call state defined in {@link PreciseCallState}
- * @param networkType network type for this call attributes
- * @param callQuality call quality for this call attributes, only CallState in
- * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call
- * quality.
- * @param callClassification call classification
- * @param imsCallId IMS call session ID for this call attributes
- * @param imsCallServiceType IMS call service type for this call attributes
- * @param imsCallType IMS call type for this call attributes
- */
- private CallState(@PreciseCallStates int callState, @NetworkType int networkType,
- @NonNull CallQuality callQuality, int callClassification, @Nullable String imsCallId,
- @ImsCallServiceType int imsCallServiceType, @ImsCallType int imsCallType) {
- this.mPreciseCallState = callState;
- this.mNetworkType = networkType;
- this.mCallQuality = callQuality;
- this.mCallClassification = callClassification;
- this.mImsCallId = imsCallId;
- this.mImsCallServiceType = imsCallServiceType;
- this.mImsCallType = imsCallType;
- }
-
- @NonNull
- @Override
- public String toString() {
- return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
- + " mCallQuality=" + mCallQuality + " mCallClassification" + mCallClassification
- + " mImsCallId=" + mImsCallId + " mImsCallServiceType=" + mImsCallServiceType
- + " mImsCallType=" + mImsCallType;
- }
-
- private CallState(Parcel in) {
- this.mPreciseCallState = in.readInt();
- this.mNetworkType = in.readInt();
- this.mCallQuality = in.readParcelable(
- CallQuality.class.getClassLoader(), CallQuality.class);
- this.mCallClassification = in.readInt();
- this.mImsCallId = in.readString();
- this.mImsCallServiceType = in.readInt();
- this.mImsCallType = in.readInt();
- }
-
- // getters
- /**
- * Returns the precise call state of the call.
- */
- @PreciseCallStates
- public int getCallState() {
- return mPreciseCallState;
- }
-
- /**
- * Returns the {@link TelephonyManager#NetworkType} of the call.
- *
- * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
- * @see TelephonyManager#NETWORK_TYPE_GPRS
- * @see TelephonyManager#NETWORK_TYPE_EDGE
- * @see TelephonyManager#NETWORK_TYPE_UMTS
- * @see TelephonyManager#NETWORK_TYPE_CDMA
- * @see TelephonyManager#NETWORK_TYPE_EVDO_0
- * @see TelephonyManager#NETWORK_TYPE_EVDO_A
- * @see TelephonyManager#NETWORK_TYPE_1xRTT
- * @see TelephonyManager#NETWORK_TYPE_HSDPA
- * @see TelephonyManager#NETWORK_TYPE_HSUPA
- * @see TelephonyManager#NETWORK_TYPE_HSPA
- * @see TelephonyManager#NETWORK_TYPE_IDEN
- * @see TelephonyManager#NETWORK_TYPE_EVDO_B
- * @see TelephonyManager#NETWORK_TYPE_LTE
- * @see TelephonyManager#NETWORK_TYPE_EHRPD
- * @see TelephonyManager#NETWORK_TYPE_HSPAP
- * @see TelephonyManager#NETWORK_TYPE_GSM
- * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA
- * @see TelephonyManager#NETWORK_TYPE_IWLAN
- * @see TelephonyManager#NETWORK_TYPE_LTE_CA
- * @see TelephonyManager#NETWORK_TYPE_NR
- */
- @NetworkType
- public int getNetworkType() {
- return mNetworkType;
- }
-
- /**
- * Returns the {#link CallQuality} of the call.
- * @return call quality for this call attributes, only CallState in {@link PreciseCallState#
- * PRECISE_CALL_STATE_ACTIVE} will have valid call quality. It will be null for the
- * call which is not in {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}.
- */
- @Nullable
- public CallQuality getCallQuality() {
- return mCallQuality;
- }
-
- /**
- * Returns the call classification.
- * @hide
- */
- public int getCallClassification() {
- return mCallClassification;
- }
-
- /**
- * Returns the IMS call session ID.
- */
- @Nullable
- public String getImsCallSessionId() {
- return mImsCallId;
- }
-
- /**
- * Returns the IMS call service type.
- */
- @ImsCallServiceType
- public int getImsCallServiceType() {
- return mImsCallServiceType;
- }
-
- /**
- * Returns the IMS call type.
- */
- @ImsCallType
- public int getImsCallType() {
- return mImsCallType;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality, mCallClassification,
- mImsCallId, mImsCallServiceType, mImsCallType);
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (o == null || !(o instanceof CallState) || hashCode() != o.hashCode()) {
- return false;
- }
-
- if (this == o) {
- return true;
- }
-
- CallState s = (CallState) o;
-
- return (Objects.equals(mPreciseCallState, s.mPreciseCallState)
- && mPreciseCallState == s.mPreciseCallState
- && mNetworkType == s.mNetworkType
- && Objects.equals(mCallQuality, s.mCallQuality)
- && mCallClassification == s.mCallClassification
- && Objects.equals(mImsCallId, s.mImsCallId)
- && mImsCallType == s.mImsCallType
- && mImsCallServiceType == s.mImsCallServiceType);
- }
-
- /**
- * {@link Parcelable#describeContents}
- */
- public int describeContents() {
- return 0;
- }
-
- /**
- * {@link Parcelable#writeToParcel}
- */
- public void writeToParcel(@Nullable Parcel dest, int flags) {
- dest.writeInt(mPreciseCallState);
- dest.writeInt(mNetworkType);
- dest.writeParcelable(mCallQuality, flags);
- dest.writeInt(mCallClassification);
- dest.writeString(mImsCallId);
- dest.writeInt(mImsCallServiceType);
- dest.writeInt(mImsCallType);
- }
-
- public static final @NonNull Creator<CallState> CREATOR = new Creator() {
- public CallState createFromParcel(Parcel in) {
- return new CallState(in);
- }
-
- public CallState[] newArray(int size) {
- return new CallState[size];
- }
- };
-
- /**
- * Builder of {@link CallState}
- *
- * <p>The example below shows how you might create a new {@code CallState}:
- *
- * <pre><code>
- *
- * CallState = new CallState.Builder()
- * .setCallState(3)
- * .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE})
- * .setCallQuality({@link CallQuality})
- * .setImsCallSessionId({@link String})
- * .setImsCallServiceType({@link ImsCallProfile#SERVICE_TYPE_NORMAL})
- * .setImsCallType({@link ImsCallProfile#CALL_TYPE_VOICE})
- * .build();
- * </code></pre>
- */
- public static final class Builder {
- private @PreciseCallStates int mPreciseCallState;
- private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- private CallQuality mCallQuality = null;
- private int mCallClassification = CALL_CLASSIFICATION_UNKNOWN;
- private String mImsCallId;
- private @ImsCallServiceType int mImsCallServiceType = ImsCallProfile.SERVICE_TYPE_NONE;
- private @ImsCallType int mImsCallType = ImsCallProfile.CALL_TYPE_NONE;
-
-
- /**
- * Default constructor for the Builder.
- */
- public Builder(@PreciseCallStates int preciseCallState) {
- mPreciseCallState = preciseCallState;
- }
-
- /**
- * Set network type of this call.
- *
- * @param networkType the transport type.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setNetworkType(@NetworkType int networkType) {
- this.mNetworkType = networkType;
- return this;
- }
-
- /**
- * Set the call quality {@link CallQuality} of this call.
- *
- * @param callQuality call quality of active call.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setCallQuality(@Nullable CallQuality callQuality) {
- this.mCallQuality = callQuality;
- return this;
- }
-
- /**
- * Set call classification for this call.
- *
- * @param classification call classification type defined in this class.
- * @return The same instance of the builder.
- * @hide
- */
- @NonNull
- public CallState.Builder setCallClassification(int classification) {
- this.mCallClassification = classification;
- return this;
- }
-
- /**
- * Set IMS call session ID of this call.
- *
- * @param imsCallId IMS call session ID.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setImsCallSessionId(@Nullable String imsCallId) {
- this.mImsCallId = imsCallId;
- return this;
- }
-
- /**
- * Set IMS call service type of this call.
- *
- * @param serviceType IMS call service type defined in {@link ImsCallProfile}.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setImsCallServiceType(@ImsCallServiceType int serviceType) {
- this.mImsCallServiceType = serviceType;
- return this;
- }
-
- /**
- * Set IMS call type of this call.
- *
- * @param callType IMS call type defined in {@link ImsCallProfile}.
- * @return The same instance of the builder.
- */
- @NonNull
- public CallState.Builder setImsCallType(@ImsCallType int callType) {
- this.mImsCallType = callType;
- return this;
- }
-
- /**
- * Build the {@link CallState}
- *
- * @return the {@link CallState} object
- */
- @NonNull
- public CallState build() {
- return new CallState(
- mPreciseCallState,
- mNetworkType,
- mCallQuality,
- mCallClassification,
- mImsCallId,
- mImsCallServiceType,
- mImsCallType);
- }
- }
-}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4ce2ca1..5c1d497 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1663,17 +1663,33 @@
}
/**
- * @return List of all SubscriptionInfo records in database,
- * include those that were inserted before, maybe empty but not null.
+ * Get all subscription info records from SIMs that are inserted now or were inserted before.
+ *
+ * <p>
+ * If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
+ * {@link SubscriptionInfo#getNumber()} will return empty string.
+ * If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
+ * {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
+ * empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
+ *
+ * <p>
+ * The carrier app will always have full {@link SubscriptionInfo} for the subscriptions
+ * that it has carrier privilege.
+ *
+ * @return List of all {@link SubscriptionInfo} records from SIMs that are inserted or
+ * inserted before. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
+ * {@link SubscriptionInfo#getSubscriptionId()}.
+ *
* @hide
*/
+ @RequiresPermission(anyOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges",
+ })
@NonNull
- @UnsupportedAppUsage
public List<SubscriptionInfo> getAllSubscriptionInfoList() {
- if (VDBG) logd("[getAllSubscriptionInfoList]+");
-
List<SubscriptionInfo> result = null;
-
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
@@ -3424,7 +3440,6 @@
/**
* Get subscriptionInfo list of subscriptions that are in the same group of given subId.
- * See {@link #createSubscriptionGroup(List)} for more details.
*
* Caller must have {@link android.Manifest.permission#READ_PHONE_STATE}
* or carrier privilege permission on the subscription.
@@ -4125,6 +4140,26 @@
}
/**
+ * Convert phone number source to string.
+ *
+ * @param source The phone name source.
+ *
+ * @return The phone name source in string format.
+ *
+ * @hide
+ */
+ @NonNull
+ public static String phoneNumberSourceToString(@PhoneNumberSource int source) {
+ switch (source) {
+ case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC: return "UICC";
+ case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER: return "CARRIER";
+ case SubscriptionManager.PHONE_NUMBER_SOURCE_IMS: return "IMS";
+ default:
+ return "UNKNOWN(" + source + ")";
+ }
+ }
+
+ /**
* Convert display name source to string.
*
* @param source The display name source.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d3ddb1b..3024b89 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -71,6 +71,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
import android.service.carrier.CarrierIdentifier;
@@ -11934,8 +11935,9 @@
}
/**
- * Gets the default Respond Via Message application, updating the cache if there is no
- * respond-via-message application currently configured.
+ * Get the component name of the default app to direct respond-via-message intent for the
+ * user associated with this subscription, update the cache if there is no respond-via-message
+ * application currently configured for this user.
* @return component name of the app and class to direct Respond Via Message intent to, or
* {@code null} if the functionality is not supported.
* @hide
@@ -11944,11 +11946,20 @@
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public @Nullable ComponentName getAndUpdateDefaultRespondViaMessageApplication() {
- return SmsApplication.getDefaultRespondViaMessageApplication(mContext, true);
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getDefaultRespondViaMessageApplication(getSubId(), true);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getAndUpdateDefaultRespondViaMessageApplication: " + e);
+ }
+ return null;
}
/**
- * Gets the default Respond Via Message application.
+ * Get the component name of the default app to direct respond-via-message intent for the
+ * user associated with this subscription.
* @return component name of the app and class to direct Respond Via Message intent to, or
* {@code null} if the functionality is not supported.
* @hide
@@ -11957,7 +11968,15 @@
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public @Nullable ComponentName getDefaultRespondViaMessageApplication() {
- return SmsApplication.getDefaultRespondViaMessageApplication(mContext, false);
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getDefaultRespondViaMessageApplication(getSubId(), false);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getDefaultRespondViaMessageApplication: " + e);
+ }
+ return null;
}
/**
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 1ea7fdc..e6d7df3 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -78,9 +78,8 @@
public static final int SERVICE_TYPE_EMERGENCY = 2;
/**
- * Call type none
+ * Call types
*/
- public static final int CALL_TYPE_NONE = 0;
/**
* IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
*/
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index abf4cde..616ea50 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -17,6 +17,7 @@
package com.android.internal.telephony;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
@@ -2618,4 +2619,14 @@
* @hide
*/
boolean isRemovableEsimDefaultEuicc(String callingPackage);
+
+ /**
+ * Get the component name of the default app to direct respond-via-message intent for the
+ * user associated with this subscription, update the cache if there is no respond-via-message
+ * application currently configured for this user.
+ * @return component name of the app and class to direct Respond Via Message intent to, or
+ * {@code null} if the functionality is not supported.
+ * @hide
+ */
+ ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
}
diff --git a/tests/TelephonyCommonTests/Android.bp b/tests/TelephonyCommonTests/Android.bp
index a9fbfd9..81ec265 100644
--- a/tests/TelephonyCommonTests/Android.bp
+++ b/tests/TelephonyCommonTests/Android.bp
@@ -47,7 +47,7 @@
// Uncomment this and comment out the jarjar rule if you want to attach a debugger and step
// through the renamed classes.
- // platform_apis: true,
+ platform_apis: true,
libs: [
"android.test.runner",
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index 7a2af72..adefac6 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -44,6 +44,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
@@ -75,9 +76,12 @@
public class SmsApplicationTest {
private static final ComponentName TEST_COMPONENT_NAME =
ComponentName.unflattenFromString("com.android.test/.TestSmsApp");
+ public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth.services";
private static final String MMS_RECEIVER_NAME = "TestMmsReceiver";
private static final String RESPOND_VIA_SMS_NAME = "TestRespondViaSmsHandler";
private static final String SEND_TO_NAME = "TestSendTo";
+ private static final String EXTERNAL_PROVIDER_CHANGE_NAME = "TestExternalProviderChangeHandler";
+ private static final String SIM_FULL_NAME = "TestSimFullHandler";
private static final int SMS_APP_UID = 10001;
private static final int FAKE_PHONE_UID = 10002;
@@ -102,6 +106,7 @@
}).collect(Collectors.toSet());
@Mock private Context mContext;
+ @Mock private Resources mResources;
@Mock private TelephonyManager mTelephonyManager;
@Mock private RoleManager mRoleManager;
@Mock private PackageManager mPackageManager;
@@ -118,6 +123,9 @@
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager);
when(mContext.createContextAsUser(isNotNull(), anyInt())).thenReturn(mContext);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getString(eq(com.android.internal.R.string.config_systemBluetoothStack)))
+ .thenReturn(BLUETOOTH_PACKAGE_NAME);
doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
.when(mPackageManager)
@@ -146,24 +154,46 @@
}
}
+
@Test
- public void testGetDefaultSmsApplication() {
+ public void testGetDefaultSmsApplicationAsUser() {
assertEquals(TEST_COMPONENT_NAME,
- SmsApplication.getDefaultSmsApplicationAsUser(mContext, false, 0));
+ SmsApplication.getDefaultSmsApplicationAsUser(mContext, false,
+ UserHandle.SYSTEM));
+ }
+
+
+ @Test
+ public void testGetDefaultMmsApplicationAsUser() {
+ ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
+ false, UserHandle.SYSTEM);
+ assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+ assertEquals(MMS_RECEIVER_NAME, componentName.getClassName());
}
@Test
- public void testGetDefaultMmsApplication() {
- assertEquals(TEST_COMPONENT_NAME,
- SmsApplication.getDefaultMmsApplicationAsUser(mContext, false,
- UserHandle.USER_SYSTEM));
+ public void testGetDefaultExternalTelephonyProviderChangedApplicationAsUser() {
+ ComponentName componentName = SmsApplication
+ .getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext,
+ false, UserHandle.SYSTEM);
+ assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+ assertEquals(EXTERNAL_PROVIDER_CHANGE_NAME, componentName.getClassName());
}
@Test
- public void testGetDefaultExternalTelephonyProviderChangedApplication() {
- assertEquals(TEST_COMPONENT_NAME,
- SmsApplication.getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext,
- false, UserHandle.USER_SYSTEM));
+ public void testGetDefaultRespondViaMessageApplicationAsUserAsUser() {
+ ComponentName componentName = SmsApplication.getDefaultRespondViaMessageApplicationAsUser(
+ mContext, false, UserHandle.SYSTEM);
+ assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+ assertEquals(RESPOND_VIA_SMS_NAME, componentName.getClassName());
+ }
+
+ @Test
+ public void testGetDefaultSimFullApplicationAsUser() {
+ ComponentName componentName = SmsApplication.getDefaultSimFullApplicationAsUser(mContext,
+ false, UserHandle.SYSTEM);
+ assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName());
+ assertEquals(SIM_FULL_NAME, componentName.getClassName());
}
@Test
@@ -174,7 +204,8 @@
setupPackageInfosForCoreApps();
assertEquals(TEST_COMPONENT_NAME,
- SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, 0));
+ SmsApplication.getDefaultSmsApplicationAsUser(mContext, true,
+ UserHandle.SYSTEM));
verify(mAppOpsManager, atLeastOnce()).setUidMode(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID,
AppOpsManager.MODE_ALLOWED);
}
@@ -251,6 +282,10 @@
return Collections.singletonList(makeRespondViaMessageResolveInfo());
case Intent.ACTION_SENDTO:
return Collections.singletonList(makeSendToResolveInfo());
+ case Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE:
+ return Collections.singletonList(makeExternalProviderChangeResolveInfo());
+ case Telephony.Sms.Intents.SIM_FULL_ACTION:
+ return Collections.singletonList(makeSimFullResolveInfo());
}
return Collections.emptyList();
}
@@ -308,4 +343,26 @@
info.activityInfo = activityInfo;
return info;
}
+
+ private ResolveInfo makeExternalProviderChangeResolveInfo() {
+ ResolveInfo info = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+
+ activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+ activityInfo.name = EXTERNAL_PROVIDER_CHANGE_NAME;
+
+ info.activityInfo = activityInfo;
+ return info;
+ }
+
+ private ResolveInfo makeSimFullResolveInfo() {
+ ResolveInfo info = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+
+ activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+ activityInfo.name = SIM_FULL_NAME;
+
+ info.activityInfo = activityInfo;
+ return info;
+ }
}
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
new file mode 100644
index 0000000..a62103e
--- /dev/null
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.telephony.tests;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.telephony.util.TelephonyUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class TelephonyUtilsTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ // Mocked classes
+ @Mock
+ private Context mContext;
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
+
+ @Before
+ public void setup() {
+ doReturn(mSubscriptionManager).when(mContext)
+ .getSystemService(eq(SubscriptionManager.class));
+ }
+
+
+ @Test
+ public void getSubscriptionUserHandle_subId_invalid() {
+ int invalidSubId = -10;
+ doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(eq(invalidSubId));
+
+ TelephonyUtils.getSubscriptionUserHandle(mContext, invalidSubId);
+
+ // getSubscriptionUserHandle should not be called if subID is inactive.
+ verify(mSubscriptionManager, never()).getSubscriptionUserHandle(eq(invalidSubId));
+ }
+
+ @Test
+ public void getSubscriptionUserHandle_subId_valid() {
+ int activeSubId = 1;
+ doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(activeSubId));
+
+ TelephonyUtils.getSubscriptionUserHandle(mContext, activeSubId);
+
+ // getSubscriptionUserHandle should be called if subID is active.
+ verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId));
+ }
+}
+
+
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
deleted file mode 100644
index 2c53f39..0000000
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
+++ /dev/null
@@ -1,158 +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.google.android.lint.aidl
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UIfExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UQualifiedReferenceExpression
-
-/**
- * Looks for methods implementing generated AIDL interface stubs
- * that can have simple permission checks migrated to
- * @EnforcePermission annotations
- *
- * TODO: b/242564870 (enable parse and autoFix of .aidl files)
- */
-@Suppress("UnstableApiUsage")
-class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
- override fun getApplicableUastTypes(): List<Class<out UElement?>> =
- listOf(UMethod::class.java)
-
- override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
-
- private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
- override fun visitMethod(node: UMethod) {
- val interfaceName = getContainingAidlInterface(node)
- .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
- val body = (node.uastBody as? UBlockExpression) ?: return
- val fix = accumulateSimplePermissionCheckFixes(body) ?: return
-
- val javaRemoveFixes = fix.locations.map {
- fix()
- .replace()
- .reformat(true)
- .range(it)
- .with("")
- .autoFix()
- .build()
- }
-
- val javaAnnotateFix = fix()
- .annotate(fix.annotation)
- .range(context.getLocation(node))
- .autoFix()
- .build()
-
- val message =
- "$interfaceName permission check can be converted to @EnforcePermission annotation"
-
- context.report(
- ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
- fix.locations.last(),
- message,
- fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
- )
- }
-
- /**
- * Walk the expressions in the method, looking for simple permission checks.
- *
- * If a single permission check is found at the beginning of the method,
- * this should be migrated to @EnforcePermission(value).
- *
- * If multiple consecutive permission checks are found,
- * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
- *
- * As soon as something other than a permission check is encountered, stop looking,
- * as some other business logic is happening that prevents an automated fix.
- */
- private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
- EnforcePermissionFix? {
- val singleFixes = mutableListOf<EnforcePermissionFix>()
- for (expression in methodBody.expressions) {
- singleFixes.add(getPermissionCheckFix(expression) ?: break)
- }
- return when (singleFixes.size) {
- 0 -> null
- 1 -> singleFixes[0]
- else -> EnforcePermissionFix.compose(singleFixes)
- }
- }
-
- /**
- * If an expression boils down to a permission check, return
- * the helper for creating a lint auto fix to @EnforcePermission
- */
- private fun getPermissionCheckFix(startingExpression: UElement?):
- EnforcePermissionFix? {
- return when (startingExpression) {
- is UQualifiedReferenceExpression -> getPermissionCheckFix(
- startingExpression.selector
- )
-
- is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
-
- is UCallExpression -> return EnforcePermissionFix
- .fromCallExpression(context, startingExpression)
-
- else -> null
- }
- }
- }
-
- companion object {
-
- private val EXPLANATION = """
- Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
- annotation to declare the permissions to be enforced. The verification code is then
- generated by the AIDL compiler, which also takes care of annotating the generated java
- code.
-
- This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
- It also enables easier auditing and review.
-
- Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
- """.trimIndent()
-
- @JvmField
- val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
- id = "UseEnforcePermissionAnnotation",
- briefDescription = "Manual permission check can be @EnforcePermission annotation",
- explanation = EXPLANATION,
- category = Category.SECURITY,
- priority = 5,
- severity = Severity.WARNING,
- implementation = Implementation(
- ManualPermissionCheckDetector::class.java,
- Scope.JAVA_FILE_SCOPE
- ),
- enabledByDefault = false, // TODO: enable once b/241171714 is resolved
- )
- }
-}
diff --git a/tools/lint/common/Android.bp b/tools/lint/common/Android.bp
new file mode 100644
index 0000000..898f88b
--- /dev/null
+++ b/tools/lint/common/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+ name: "AndroidCommonLint",
+ srcs: ["src/main/java/**/*.kt"],
+ libs: ["lint_api"],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/Constants.kt
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
new file mode 100644
index 0000000..720f835
--- /dev/null
+++ b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt
@@ -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.google.android.lint
+
+import com.android.tools.lint.detector.api.getUMethod
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+
+fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
+ val method = callExpression.resolve()?.getUMethod() ?: return false
+ return hasPermissionMethodAnnotation(method)
+}
+
+fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+ .any {
+ it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
+ }
+
+fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
+ it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt b/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
rename to tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
new file mode 100644
index 0000000..5f6c6f7
--- /dev/null
+++ b/tools/lint/fix/Android.bp
@@ -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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+python_binary_host {
+ name: "lint_fix",
+ main: "lint_fix.py",
+ srcs: ["lint_fix.py"],
+}
diff --git a/tools/lint/Android.bp b/tools/lint/framework/Android.bp
similarity index 87%
rename from tools/lint/Android.bp
rename to tools/lint/framework/Android.bp
index 96618f4..7f27e8a 100644
--- a/tools/lint/Android.bp
+++ b/tools/lint/framework/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 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.
@@ -29,6 +29,11 @@
"auto_service_annotations",
"lint_api",
],
+ static_libs: [
+ "AndroidCommonLint",
+ // TODO: remove once b/236558918 is resolved and the below checks actually run globally
+ "AndroidGlobalLintChecker",
+ ],
kotlincflags: ["-Xjvm-default=all"],
}
@@ -51,9 +56,3 @@
unit_test: true,
},
}
-
-python_binary_host {
- name: "lint_fix",
- main: "fix/lint_fix.py",
- srcs: ["fix/lint_fix.py"],
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
similarity index 93%
rename from tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 741655b..413e197 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -21,7 +21,7 @@
import com.android.tools.lint.detector.api.CURRENT_API
import com.google.android.lint.aidl.EnforcePermissionDetector
import com.google.android.lint.aidl.EnforcePermissionHelperDetector
-import com.google.android.lint.aidl.ManualPermissionCheckDetector
+import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
import com.google.android.lint.parcel.SaferParcelChecker
import com.google.auto.service.AutoService
@@ -40,7 +40,7 @@
EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
- ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+ SimpleManualPermissionEnforcementDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
similarity index 97%
rename from tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
index 1b0f035..e12ec3d 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
@@ -26,7 +26,6 @@
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.getUMethod
-import com.google.android.lint.aidl.hasPermissionMethodAnnotation
import com.intellij.psi.PsiType
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UBlockExpression
@@ -193,5 +192,8 @@
else -> false
}
}
+
+ private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+ .any { it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD) }
}
}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
rename to tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
rename to tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
diff --git a/tools/lint/Android.bp b/tools/lint/global/Android.bp
similarity index 84%
copy from tools/lint/Android.bp
copy to tools/lint/global/Android.bp
index 96618f4..3756abe 100644
--- a/tools/lint/Android.bp
+++ b/tools/lint/global/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 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.
@@ -22,18 +22,22 @@
}
java_library_host {
- name: "AndroidFrameworkLintChecker",
+ name: "AndroidGlobalLintChecker",
srcs: ["checks/src/main/java/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
"lint_api",
],
+ static_libs: ["AndroidCommonLint"],
kotlincflags: ["-Xjvm-default=all"],
+ dist: {
+ targets: ["droid"],
+ },
}
java_test_host {
- name: "AndroidFrameworkLintCheckerTest",
+ name: "AndroidGlobalLintCheckerTest",
// TODO(b/239881504): Since this test was written, Android
// Lint was updated, and now includes classes that were
// compiled for java 15. The soong build doesn't support
@@ -42,7 +46,7 @@
enabled: false,
srcs: ["checks/src/test/java/**/*.kt"],
static_libs: [
- "AndroidFrameworkLintChecker",
+ "AndroidGlobalLintChecker",
"junit",
"lint",
"lint_tests",
@@ -51,9 +55,3 @@
unit_test: true,
},
}
-
-python_binary_host {
- name: "lint_fix",
- main: "fix/lint_fix.py",
- srcs: ["fix/lint_fix.py"],
-}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
new file mode 100644
index 0000000..b377d50
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.google.android.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
+import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class AndroidGlobalIssueRegistry : IssueRegistry() {
+ override val issues = listOf(
+ EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+ EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+ EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+ SimpleManualPermissionEnforcementDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+ )
+
+ override val api: Int
+ get() = CURRENT_API
+
+ override val minApi: Int
+ get() = 8
+
+ override val vendor: Vendor = Vendor(
+ vendorName = "Android",
+ feedbackUrl = "http://b/issues/new?component=315013",
+ contact = "repsonsible-apis@google.com"
+ )
+}
\ No newline at end of file
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
new file mode 100644
index 0000000..227cdcd
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+/**
+ * Abstract class for detectors that look for methods implementing
+ * generated AIDL interface stubs
+ */
+abstract class AidlImplementationDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+ listOf(UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+ private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ val interfaceName = getContainingAidlInterface(node)
+ .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
+ val body = (node.uastBody as? UBlockExpression) ?: return
+ visitAidlMethod(context, node, interfaceName, body)
+ }
+ }
+
+ abstract fun visitAidlMethod(
+ context: JavaContext,
+ node: UMethod,
+ interfaceName: String,
+ body: UBlockExpression,
+ )
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
similarity index 97%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index d120e1d..f1b6348 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -19,6 +19,8 @@
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Location
import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.hasPermissionNameAnnotation
+import com.google.android.lint.isPermissionMethodCall
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.evaluateString
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
similarity index 100%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
similarity index 69%
rename from tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
index edbdd8d..250ca78 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -16,14 +16,9 @@
package com.google.android.lint.aidl
-import com.android.tools.lint.detector.api.getUMethod
-import com.google.android.lint.ANNOTATION_PERMISSION_METHOD
-import com.google.android.lint.ANNOTATION_PERMISSION_NAME
import com.google.android.lint.CLASS_STUB
import com.intellij.psi.PsiAnonymousClass
-import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UParameter
/**
* Given a UMethod, determine if this method is
@@ -51,17 +46,3 @@
it.referenceName == CLASS_STUB
} ?: false
}
-
-fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
- val method = callExpression.resolve()?.getUMethod() ?: return false
- return hasPermissionMethodAnnotation(method)
-}
-
-fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
- .any {
- it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
- }
-
-fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
- it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
-}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
new file mode 100644
index 0000000..4c0cbe7
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+
+/**
+ * Looks for methods implementing generated AIDL interface stubs
+ * that can have simple permission checks migrated to
+ * @EnforcePermission annotations
+ *
+ * TODO: b/242564870 (enable parse and autoFix of .aidl files)
+ */
+@Suppress("UnstableApiUsage")
+class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() {
+ override fun visitAidlMethod(
+ context: JavaContext,
+ node: UMethod,
+ interfaceName: String,
+ body: UBlockExpression
+ ) {
+ val fix = accumulateSimplePermissionCheckFixes(body, context) ?: return
+
+ val javaRemoveFixes = fix.locations.map {
+ fix()
+ .replace()
+ .reformat(true)
+ .range(it)
+ .with("")
+ .autoFix()
+ .build()
+ }
+
+ val javaAnnotateFix = fix()
+ .annotate(fix.annotation)
+ .range(context.getLocation(node))
+ .autoFix()
+ .build()
+
+ context.report(
+ ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+ fix.locations.last(),
+ "$interfaceName permission check can be converted to @EnforcePermission annotation",
+ fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
+ )
+ }
+
+ /**
+ * Walk the expressions in the method, looking for simple permission checks.
+ *
+ * If a single permission check is found at the beginning of the method,
+ * this should be migrated to @EnforcePermission(value).
+ *
+ * If multiple consecutive permission checks are found,
+ * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
+ *
+ * As soon as something other than a permission check is encountered, stop looking,
+ * as some other business logic is happening that prevents an automated fix.
+ */
+ private fun accumulateSimplePermissionCheckFixes(
+ methodBody: UBlockExpression,
+ context: JavaContext
+ ):
+ EnforcePermissionFix? {
+ val singleFixes = mutableListOf<EnforcePermissionFix>()
+ for (expression in methodBody.expressions) {
+ singleFixes.add(getPermissionCheckFix(expression, context) ?: break)
+ }
+ return when (singleFixes.size) {
+ 0 -> null
+ 1 -> singleFixes[0]
+ else -> EnforcePermissionFix.compose(singleFixes)
+ }
+ }
+
+ /**
+ * If an expression boils down to a permission check, return
+ * the helper for creating a lint auto fix to @EnforcePermission
+ */
+ private fun getPermissionCheckFix(startingExpression: UElement?, context: JavaContext):
+ EnforcePermissionFix? {
+ return when (startingExpression) {
+ is UQualifiedReferenceExpression -> getPermissionCheckFix(
+ startingExpression.selector, context
+ )
+
+ is UIfExpression -> getPermissionCheckFix(startingExpression.condition, context)
+
+ is UCallExpression -> return EnforcePermissionFix
+ .fromCallExpression(context, startingExpression)
+
+ else -> null
+ }
+ }
+
+ companion object {
+
+ private val EXPLANATION = """
+ Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
+ annotation to declare the permissions to be enforced. The verification code is then
+ generated by the AIDL compiler, which also takes care of annotating the generated java
+ code.
+
+ This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
+ It also enables easier auditing and review.
+
+ Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
+ """.trimIndent()
+
+ @JvmField
+ val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
+ id = "SimpleManualPermissionEnforcement",
+ briefDescription = "Manual permission check can be @EnforcePermission annotation",
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 5,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ SimpleManualPermissionEnforcementDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ ),
+ enabledByDefault = false, // TODO: enable once b/241171714 is resolved
+ )
+ }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
similarity index 93%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
index d4a3497..150fc26 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
@@ -23,10 +23,10 @@
import com.android.tools.lint.detector.api.Issue
@Suppress("UnstableApiUsage")
-class ManualPermissionCheckDetectorTest : LintDetectorTest() {
- override fun getDetector(): Detector = ManualPermissionCheckDetector()
+class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = SimpleManualPermissionEnforcementDetector()
override fun getIssues(): List<Issue> = listOf(
- ManualPermissionCheckDetector
+ SimpleManualPermissionEnforcementDetector
.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION
)
@@ -52,7 +52,7 @@
.run()
.expect(
"""
- src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -92,7 +92,7 @@
.run()
.expect(
"""
- src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(
^
0 errors, 1 warnings
@@ -132,7 +132,7 @@
.run()
.expect(
"""
- src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -174,7 +174,7 @@
.run()
.expect(
"""
- src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission(
^
0 errors, 1 warnings
@@ -243,7 +243,7 @@
.run()
.expect(
"""
- src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
helper();
~~~~~~~~~
0 errors, 1 warnings
@@ -289,7 +289,7 @@
.run()
.expect(
"""
- src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
mContext.enforceCallingOrSelfPermission("FOO", "foo");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -340,7 +340,7 @@
.run()
.expect(
"""
- src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
helperHelper();
~~~~~~~~~~~~~~~
0 errors, 1 warnings
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
similarity index 100%
rename from tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
rename to tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt