Merge "Propogate face/fingerprint authenticate options to detect and authenticate clients." into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4088a48..bf3789f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1476,7 +1476,8 @@
/* timingDelayConstraintSatisfied */ false,
/* isDeviceIdle */ false,
/* hasConnectivityConstraintSatisfied */ false,
- /* hasContentTriggerConstraintSatisfied */ false);
+ /* hasContentTriggerConstraintSatisfied */ false,
+ 0);
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1894,7 +1895,8 @@
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
- cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+ 0);
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index dcfadca..29ab455 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -461,7 +461,8 @@
job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
- job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+ mExecutionStartTimeElapsed - job.enqueueTime);
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
// Use the context's ID to distinguish traces since there'll only be one job
// running per context.
@@ -1356,7 +1357,8 @@
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
- completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+ 0);
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/core/api/current.txt b/core/api/current.txt
index fcec790..2161186 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13577,17 +13577,26 @@
}
public final class CreateCredentialRequest implements android.os.Parcelable {
- ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean, boolean);
method public boolean alwaysSendAppInfoToProvider();
method public int describeContents();
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialData();
+ method @Nullable public String getOrigin();
method @NonNull public String getType();
method public boolean isSystemProviderRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
}
+ public static final class CreateCredentialRequest.Builder {
+ ctor public CreateCredentialRequest.Builder(@NonNull android.os.Bundle, @NonNull android.os.Bundle);
+ method @NonNull public android.credentials.CreateCredentialRequest build();
+ method @NonNull public android.credentials.CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean);
+ method @NonNull public android.credentials.CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public android.credentials.CreateCredentialRequest.Builder setOrigin(@NonNull String);
+ method @NonNull public android.credentials.CreateCredentialRequest.Builder setType(@NonNull String);
+ }
+
public final class CreateCredentialResponse implements android.os.Parcelable {
ctor public CreateCredentialResponse(@NonNull android.os.Bundle);
method public int describeContents();
@@ -13619,9 +13628,7 @@
public final class CredentialManager {
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.ClearCredentialStateException>);
method public void createCredential(@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.CreateCredentialException>);
- method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void createCredentialWithOrigin(@NonNull android.credentials.CreateCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
method public void getCredential(@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.GetCredentialException>);
- method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void getCredentialWithOrigin(@NonNull android.credentials.GetCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
@@ -13656,6 +13663,7 @@
method public int describeContents();
method @NonNull public java.util.List<android.credentials.CredentialOption> getCredentialOptions();
method @NonNull public android.os.Bundle getData();
+ method @Nullable public String getOrigin();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialRequest> CREATOR;
}
@@ -13666,6 +13674,7 @@
method @NonNull public android.credentials.GetCredentialRequest build();
method @NonNull public android.credentials.GetCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean);
method @NonNull public android.credentials.GetCredentialRequest.Builder setCredentialOptions(@NonNull java.util.List<android.credentials.CredentialOption>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public android.credentials.GetCredentialRequest.Builder setOrigin(@NonNull String);
}
public final class GetCredentialResponse implements android.os.Parcelable {
@@ -19361,7 +19370,7 @@
public final class OutputConfiguration implements android.os.Parcelable {
ctor public OutputConfiguration(@NonNull android.view.Surface);
ctor public OutputConfiguration(int, @NonNull android.view.Surface);
- ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+ ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
method public void addSensorPixelModeUsed(int);
method public void addSurface(@NonNull android.view.Surface);
method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
@@ -40563,7 +40572,9 @@
public final class CallingAppInfo implements android.os.Parcelable {
ctor public CallingAppInfo(@NonNull String, @NonNull android.content.pm.SigningInfo);
+ ctor public CallingAppInfo(@NonNull String, @NonNull android.content.pm.SigningInfo, @Nullable String);
method public int describeContents();
+ method @Nullable public String getOrigin();
method @NonNull public String getPackageName();
method @NonNull public android.content.pm.SigningInfo getSigningInfo();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -42055,6 +42066,7 @@
}
public final class CallControl {
+ method public void answer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index e97e711..d62e15a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -65,12 +65,12 @@
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
import android.os.StrictMode;
import android.os.WorkSource;
import android.service.voice.IVoiceInteractionSession;
import android.view.IRecentsAnimationRunner;
import android.view.IRemoteAnimationRunner;
-import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.window.IWindowOrganizerController;
@@ -349,12 +349,13 @@
/**
* Prepare the back navigation in the server. This setups the leashed for sysui to animate
* the back gesture and returns the data needed for the animation.
- * @param focusObserver a remote callback to nofify shell when the focused window lost focus.
+ * @param navigationObserver a remote callback to nofify shell when the focused window is gone,
+ or an unexpected transition has happened on the navigation target.
* @param adaptor a remote animation to be run for the back navigation plays the animation.
* @return Returns the back navigation info.
*/
android.window.BackNavigationInfo startBackNavigation(
- in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor);
+ in RemoteCallback navigationObserver, in BackAnimationAdapter adaptor);
/**
* registers a callback to be invoked when the screen is captured.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ef5cd93..440ee20 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5758,7 +5758,7 @@
List<Notification.Action> nonContextualActions = getNonContextualActions();
int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
- boolean emphazisedMode = mN.fullScreenIntent != null
+ boolean emphasizedMode = mN.fullScreenIntent != null
|| p.mCallStyleActions
|| ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
@@ -5771,7 +5771,7 @@
big.setInt(R.id.actions, "setCollapsibleIndentDimen",
R.dimen.call_notification_collapsible_indent);
}
- big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
+ big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
if (numActions > 0 && !p.mHideActions) {
big.setViewVisibility(R.id.actions_container, View.VISIBLE);
big.setViewVisibility(R.id.actions, View.VISIBLE);
@@ -5783,12 +5783,12 @@
boolean actionHasValidInput = hasValidRemoteInput(action);
validRemoteInput |= actionHasValidInput;
- final RemoteViews button = generateActionButton(action, emphazisedMode, p);
- if (actionHasValidInput && !emphazisedMode) {
+ final RemoteViews button = generateActionButton(action, emphasizedMode, p);
+ if (actionHasValidInput && !emphasizedMode) {
// Clear the drawable
button.setInt(R.id.action0, "setBackgroundResource", 0);
}
- if (emphazisedMode && i > 0) {
+ if (emphasizedMode && i > 0) {
// Clear start margin from non-first buttons to reduce the gap between them.
// (8dp remaining gap is from all buttons' standard 4dp inset).
button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
@@ -7475,10 +7475,10 @@
Resources resources = context.getResources();
boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
if (mPictureIcon != null) {
- int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
+ int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_height_low_ram
: R.dimen.notification_big_picture_max_height);
- int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
+ int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_width_low_ram
: R.dimen.notification_big_picture_max_width);
mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 50a7da1..5ee10a5 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -115,6 +115,20 @@
String SHELL_COMMAND_CONFIRM_TIME = "confirm_time";
/**
+ * A shell command that clears the network time signal used by {@link
+ * SystemClock#currentNetworkTimeClock()}.
+ * @hide
+ */
+ String SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME = "clear_system_clock_network_time";
+
+ /**
+ * A shell command that sets the network time signal used by {@link
+ * SystemClock#currentNetworkTimeClock()}.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME = "set_system_clock_network_time";
+
+ /**
* A shared utility method to create a {@link ManualTimeSuggestion}.
*
* @hide
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index e981581..2b400c1f 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -155,6 +155,11 @@
AttributionSource(@NonNull Parcel in) {
this(AttributionSourceState.CREATOR.createFromParcel(in));
+ if (!Binder.isDirectlyHandlingTransaction()) {
+ throw new SecurityException("AttributionSource should be unparceled during a binder "
+ + "transaction for proper verification.");
+ }
+
// Since we just unpacked this object as part of it transiting a Binder
// call, this is the perfect time to enforce that its UID and PID can be trusted
enforceCallingUidAndPid();
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5818ed7..85daf15 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3234,8 +3234,9 @@
/**
* Broadcast Action: The receiver's effective locale has changed.
*
- * This happens when the device locale, or the receiving app's locale
- * (set via {@link android.app.LocaleManager#setApplicationLocales}) changed.
+ * This happens when the device locale, the receiving app's locale
+ * (set via {@link android.app.LocaleManager#setApplicationLocales}) or language tags
+ * of Regional preferences changed.
*
* Can be received by manifest-declared receivers.
*
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8acdf51..a6a6215 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1711,8 +1711,8 @@
* performed on the session. In case of device reboot or data loader transient failure
* before the session has been finalized, you may commit the session again.
* <p>
- * If the installer is the device owner or the affiliated profile owner, there will be no
- * user intervention.
+ * If the installer is the device owner, the affiliated profile owner, or has received
+ * user pre-approval of this session, there will be no user intervention.
*
* @param statusReceiver Called when the state of the session changes. Intents
* sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
@@ -1722,6 +1722,7 @@
* {@link #openWrite(String, long, long)} are still open.
*
* @see android.app.admin.DevicePolicyManager
+ * @see #requestUserPreapproval
*/
public void commit(@NonNull IntentSender statusReceiver) {
try {
@@ -1987,14 +1988,22 @@
* {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
* permission, they can request the approval from users before
* {@link Session#commit(IntentSender)} is called. This may require user intervention as
- * well. The result of the request will be reported through the given callback.
+ * well. When user intervention is required, installers will receive a
+ * {@link #STATUS_PENDING_USER_ACTION} callback, and {@link #STATUS_SUCCESS} otherwise.
+ * In case that requesting user pre-approval is not available, installers will receive
+ * {@link #STATUS_FAILURE_BLOCKED} instead. Note that if the users decline the request,
+ * this session will be abandoned.
+ *
+ * If user intervention is required but never resolved, or requesting user
+ * pre-approval is not available, you may still call {@link Session#commit(IntentSender)}
+ * as the typical installation.
*
* @param details the adequate context to this session for requesting the approval from
* users prior to commit.
* @param statusReceiver called when the state of the session changes.
- * Intents sent to this receiver contain
- * {@link #EXTRA_STATUS}. Refer to the individual
- * status codes on how to handle them.
+ * Intents sent to this receiver contain {@link #EXTRA_STATUS}
+ * and the {@link #EXTRA_PRE_APPROVAL} would be {@code true}.
+ * Refer to the individual status codes on how to handle them.
*
* @throws IllegalArgumentException when {@link PreapprovalDetails} is {@code null}.
* @throws IllegalArgumentException if {@link IntentSender} is {@code null}.
@@ -2003,6 +2012,7 @@
* @throws IllegalStateException if called again after this method has been called on
* this session.
* @throws SecurityException when the caller does not own this session.
+ * @throws SecurityException if called after the session has been committed or abandoned.
*/
public void requestUserPreapproval(@NonNull PreapprovalDetails details,
@NonNull IntentSender statusReceiver) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index f3ad9d1..02b9308 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -361,7 +361,7 @@
/**
* Constant corresponding to {@code systemExempted} in
* the {@link android.R.attr#foregroundServiceType} attribute.
- * The system exmpted foreground service use cases.
+ * The system exempted foreground service use cases.
*
* <p class="note">Note, apps are allowed to use this type only in the following cases:
* <ul>
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 1e2a86f..b756a43 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -16,9 +16,14 @@
package android.credentials;
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -69,6 +74,14 @@
private final boolean mIsSystemProviderRequired;
/**
+ * The origin of the calling app. Callers of this special API (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get credentials
+ * on behalf of that app.
+ */
+ @Nullable
+ private final String mOrigin;
+
+ /**
* Returns the requested credential type.
*/
@NonNull
@@ -123,6 +136,14 @@
return mAlwaysSendAppInfoToProvider;
}
+ /**
+ * Returns the origin of the calling app if set otherwise returns null.
+ */
+ @Nullable
+ public String getOrigin() {
+ return mOrigin;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
@@ -130,6 +151,7 @@
dest.writeBundle(mCandidateQueryData);
dest.writeBoolean(mIsSystemProviderRequired);
dest.writeBoolean(mAlwaysSendAppInfoToProvider);
+ dest.writeString8(mOrigin);
}
@Override
@@ -146,6 +168,7 @@
+ ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ ", alwaysSendAppInfoToProvider="
+ mAlwaysSendAppInfoToProvider
+ + ", origin=" + mOrigin
+ "}";
}
@@ -165,21 +188,26 @@
* the query phase, and will only be sent along
* with the final request, after the user has selected
* an entry on the UI.
+ * @param origin the origin of the calling app. Callers of this special setter (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get
+ * credentials on behalf of that app.
*
* @throws IllegalArgumentException If type is empty.
*/
- public CreateCredentialRequest(
+ private CreateCredentialRequest(
@NonNull String type,
@NonNull Bundle credentialData,
@NonNull Bundle candidateQueryData,
boolean isSystemProviderRequired,
- boolean alwaysSendAppInfoToProvider) {
+ boolean alwaysSendAppInfoToProvider,
+ @NonNull String origin) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
mIsSystemProviderRequired = isSystemProviderRequired;
mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+ mOrigin = origin;
}
private CreateCredentialRequest(@NonNull Parcel in) {
@@ -188,6 +216,7 @@
Bundle candidateQueryData = in.readBundle();
boolean isSystemProviderRequired = in.readBoolean();
boolean alwaysSendAppInfoToProvider = in.readBoolean();
+ mOrigin = in.readString8();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
@@ -211,4 +240,104 @@
return new CreateCredentialRequest(in);
}
};
+
+ /** A builder for {@link CreateCredentialRequest}. */
+ public static final class Builder {
+
+ private boolean mAlwaysSendAppInfoToProvider;
+
+ @NonNull
+ private String mType;
+
+ @NonNull
+ private final Bundle mCredentialData;
+
+ @NonNull
+ private final Bundle mCandidateQueryData;
+
+ private boolean mIsSystemProviderRequired;
+
+ private String mOrigin;
+
+ /**
+ * @param credentialData the full credential creation request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial creation candidate query stage
+ */
+ public Builder(@NonNull Bundle credentialData, @NonNull Bundle candidateQueryData) {
+ mCredentialData = requireNonNull(credentialData,
+ "credentialData must not be null");
+ mCandidateQueryData = requireNonNull(candidateQueryData,
+ "candidateQueryData must not be null");
+ }
+
+ /**
+ * Sets a true/false value to determine if the calling app info should be
+ * removed from the request that is sent to the providers.
+ *
+ * Developers must set this to false if they wish to remove the
+ * {@link android.service.credentials.CallingAppInfo} from the query phases requests that
+ * providers receive. Note that the calling app info will still be sent in the
+ * final phase after the user has made a selection on the UI.
+ *
+ * If not set, the default value will be true and the calling app info will be
+ * propagated to the providers in every phase.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean value) {
+ mAlwaysSendAppInfoToProvider = value;
+ return this;
+ }
+
+ /**
+ * Sets the requested credential type.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public CreateCredentialRequest.Builder setType(@NonNull String type) {
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Sets whether the request must only be fulfilled by a system provider.
+ * This defaults to false
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean value) {
+ mIsSystemProviderRequired = value;
+ return this;
+ }
+
+ /**
+ * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get
+ * credentials on behalf of that app. The permission check only happens later when this
+ * instance is passed and processed by the Credential Manager.
+ */
+ @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
+ @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+ @NonNull
+ public CreateCredentialRequest.Builder setOrigin(@NonNull String origin) {
+ mOrigin = origin;
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetCredentialRequest}.
+ *
+ * @throws IllegalArgumentException If credentialOptions is empty.
+ */
+ @NonNull
+ public CreateCredentialRequest build() {
+ Preconditions.checkStringNotEmpty(
+ mType,
+ "type must not be empty");
+
+ return new CreateCredentialRequest(mType, mCredentialData, mCandidateQueryData,
+ mIsSystemProviderRequired, mAlwaysSendAppInfoToProvider, mOrigin);
+ }
+ }
}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 87281cc..f0230e7 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -123,6 +123,10 @@
*
* <p>The execution can potentially launch UI flows to collect user consent to using a
* credential, display a picker when multiple credentials exist, etc.
+ * Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an
+ * app different from their own, to be able to get credentials on behalf of that app. They would
+ * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * to use this functionality
*
* @param request the request specifying type(s) of credentials to get from the user
* @param activity the activity used to launch any UI needed
@@ -163,61 +167,14 @@
}
/**
- * Launches the necessary flows to retrieve an app credential from the user, for the given
- * origin.
- *
- * <p>The execution can potentially launch UI flows to collect user consent to using a
- * credential, display a picker when multiple credentials exist, etc.
- *
- * @param request the request specifying type(s) of credentials to get from the user
- * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
- * can set this origin for an app different from their own, to be able to get credentials
- * on behalf of that app.
- * @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
- */
- @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
- public void getCredentialWithOrigin(
- @NonNull GetCredentialRequest request,
- @Nullable String origin,
- @NonNull Activity activity,
- @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> 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");
-
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "getCredential already canceled");
- return;
- }
-
- ICancellationSignal cancelRemote = null;
- try {
- cancelRemote =
- mService.executeGetCredentialWithOrigin(
- request,
- new GetCredentialTransport(activity, executor, callback),
- mContext.getOpPackageName(),
- origin);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
-
- if (cancellationSignal != null && cancelRemote != null) {
- cancellationSignal.setRemote(cancelRemote);
- }
- }
-
- /**
* Launches the necessary flows to register an app credential for the user.
*
* <p>The execution can potentially launch UI flows to collect user consent to creating or
* storing the new credential, etc.
+ * Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an
+ * app different from their own, to be able to get credentials on behalf of that app. They would
+ * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * to use this functionality
*
* @param request the request specifying type(s) of credentials to get from the user
* @param activity the activity used to launch any UI needed
@@ -259,58 +216,6 @@
}
/**
- * Launches the necessary flows to register an app credential for the user.
- *
- * <p>The execution can potentially launch UI flows to collect user consent to creating or
- * storing the new credential, etc.
- *
- * @param request the request specifying type(s) of credentials to get from the user, for the
- * given origin
- * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
- * can set this origin for an app different from their own, to be able to get credentials
- * on behalf of that app.
- * @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
- */
- @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
- public void createCredentialWithOrigin(
- @NonNull CreateCredentialRequest request,
- @Nullable String origin,
- @NonNull Activity activity,
- @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull
- OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> 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");
-
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "createCredential already canceled");
- return;
- }
-
- ICancellationSignal cancelRemote = null;
- try {
- cancelRemote =
- mService.executeCreateCredentialWithOrigin(
- request,
- new CreateCredentialTransport(activity, executor, callback),
- mContext.getOpPackageName(),
- origin);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
-
- if (cancellationSignal != null && cancelRemote != null) {
- cancellationSignal.setRemote(cancelRemote);
- }
- }
-
- /**
* Clears the current user credential state from all credential providers.
*
* <p>You should invoked this api after your user signs out of your app to notify all credential
diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java
index bc92c7c..951cbe4 100644
--- a/core/java/android/credentials/GetCredentialRequest.java
+++ b/core/java/android/credentials/GetCredentialRequest.java
@@ -16,9 +16,13 @@
package android.credentials;
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Parcel;
@@ -49,6 +53,14 @@
private final Bundle mData;
/**
+ * The origin of the calling app. Callers of this special API (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get credentials
+ * on behalf of that app.
+ */
+ @Nullable
+ private String mOrigin;
+
+ /**
* True/False value to determine if the calling app info should be
* removed from the request that is sent to the providers.
* Developers must set this to false if they wish to remove the
@@ -76,6 +88,14 @@
}
/**
+ * Returns the origin of the calling app if set otherwise returns null.
+ */
+ @Nullable
+ public String getOrigin() {
+ return mOrigin;
+ }
+
+ /**
* Returns a value to determine if the calling app info should be always
* sent to the provider in every phase (if true), or should be removed
* from the query phase, and only sent as part of the request in the final phase,
@@ -90,6 +110,7 @@
dest.writeTypedList(mCredentialOptions, flags);
dest.writeBundle(mData);
dest.writeBoolean(mAlwaysSendAppInfoToProvider);
+ dest.writeString8(mOrigin);
}
@Override
@@ -103,11 +124,12 @@
+ ", data=" + mData
+ ", alwaysSendAppInfoToProvider="
+ mAlwaysSendAppInfoToProvider
+ + ", origin=" + mOrigin
+ "}";
}
private GetCredentialRequest(@NonNull List<CredentialOption> credentialOptions,
- @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider) {
+ @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider, String origin) {
Preconditions.checkCollectionNotEmpty(
credentialOptions,
/*valueName=*/ "credentialOptions");
@@ -118,6 +140,7 @@
mData = requireNonNull(data,
"data must not be null");
mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+ mOrigin = origin;
}
private GetCredentialRequest(@NonNull Parcel in) {
@@ -132,6 +155,7 @@
AnnotationValidations.validate(NonNull.class, null, mData);
mAlwaysSendAppInfoToProvider = in.readBoolean();
+ mOrigin = in.readString8();
}
@NonNull public static final Parcelable.Creator<GetCredentialRequest> CREATOR =
@@ -159,6 +183,8 @@
@NonNull
private boolean mAlwaysSendAppInfoToProvider = true;
+ private String mOrigin;
+
/**
* @param data the top request level data
*/
@@ -209,6 +235,20 @@
}
/**
+ * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get
+ * credentials on behalf of that app. The permission check only happens later when this
+ * instance is passed and processed by the Credential Manager.
+ */
+ @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
+ @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+ @NonNull
+ public Builder setOrigin(@NonNull String origin) {
+ mOrigin = origin;
+ return this;
+ }
+
+ /**
* Builds a {@link GetCredentialRequest}.
*
* @throws IllegalArgumentException If credentialOptions is empty.
@@ -222,7 +262,7 @@
mCredentialOptions,
/*valueName=*/ "credentialOptions");
return new GetCredentialRequest(mCredentialOptions, mData,
- mAlwaysSendAppInfoToProvider);
+ mAlwaysSendAppInfoToProvider, mOrigin);
}
}
}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 6d81d80..625fc8a 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -41,12 +41,8 @@
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal executeGetCredentialWithOrigin(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage, String origin);
-
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal executeCreateCredentialWithOrigin(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage, String origin);
-
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
@nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 67634dc..3c10e81 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -78,7 +78,7 @@
Resources.getSystem()
.getString(
com.android.internal.R.string
- .config_credentialManagerDialogComponent));
+ .config_credentialManagerReceiverComponent));
intent.setComponent(componentName);
intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
return intent;
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 19719a8..fbc0184 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -142,24 +142,18 @@
PackageManager.PERMISSION_GRANTED;
}
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
mFoldStateListener = new FoldStateListener(context);
try {
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ context.getSystemService(DeviceStateManager.class).registerCallback(
+ new HandlerExecutor(CameraManagerGlobal.get().getDeviceStateHandler()),
+ mFoldStateListener);
} catch (IllegalStateException e) {
Log.v(TAG, "Failed to register device state listener!");
Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
- mHandlerThread.quitSafely();
- mHandler = null;
mFoldStateListener = null;
}
}
- private HandlerThread mHandlerThread;
- private Handler mHandler;
private FoldStateListener mFoldStateListener;
/**
@@ -1645,6 +1639,9 @@
private ICameraService mCameraService;
private boolean mHasOpenCloseListenerPermission = false;
+ private HandlerThread mDeviceStateHandlerThread;
+ private Handler mDeviceStateHandler;
+
// Singleton, don't allow construction
private CameraManagerGlobal() { }
@@ -1658,6 +1655,18 @@
return gCameraManager;
}
+ public Handler getDeviceStateHandler() {
+ synchronized(mLock) {
+ if (mDeviceStateHandlerThread == null) {
+ mDeviceStateHandlerThread = new HandlerThread(TAG);
+ mDeviceStateHandlerThread.start();
+ mDeviceStateHandler = new Handler(mDeviceStateHandlerThread.getLooper());
+ }
+
+ return mDeviceStateHandler;
+ }
+ }
+
@Override
public IBinder asBinder() {
return this;
diff --git a/core/java/android/os/CancellationSignalBeamer.java b/core/java/android/os/CancellationSignalBeamer.java
new file mode 100644
index 0000000..afb5ff7
--- /dev/null
+++ b/core/java/android/os/CancellationSignalBeamer.java
@@ -0,0 +1,325 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.system.SystemCleaner;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.HashMap;
+
+/**
+ * A transport for {@link CancellationSignal}, but unlike
+ * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the
+ * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable
+ * request.
+ *
+ * <p><strong>Important:</strong> For this to work, the following invariants must be held up:
+ * <ul>
+ * <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result
+ * (otherwise, the token will be leaked and cancellation isn't propagated), and that call
+ * must happen after the call using the
+ * token is sent (otherwise, any concurrent cancellation may be lost). It is strongly
+ * recommended to use try-with-resources on the token.
+ * <li>The cancel(), forget() and cancellable operations transporting the token must either
+ * all be oneway on the same binder, or all be non-oneway to guarantee proper ordering.
+ * <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there
+ * can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}.
+ *
+ * </ul>
+ * <p>Caveats:
+ * <ul>
+ * <li>Cancellation is only ever dispatched after the token is closed, and thus after the
+ * call performing the cancellable operation (if the invariants are followed). The operation
+ * must therefore not block the incoming binder thread, or cancellation won't be possible.
+ * <li>Consequently, in the unlikely event that the sender dies right after beaming an already
+ * cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with
+ * {@link CancellationSignal#createTransport()}).
+ * <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources
+ * / when closing the token. If the receiver is in the same process, and the signal is
+ * already cancelled, this may invoke the target's OnCancelListener during that phase.
+ * </ul>
+ *
+ *
+ * <p>Usage:
+ * <pre>
+ * // Sender:
+ *
+ * class FooManager {
+ * var mCancellationSignalSender = new CancellationSignalBeamer.Sender() {
+ * @Override
+ * public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); }
+ *
+ * @Override
+ * public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); }
+ * };
+ *
+ * public void doCancellableOperation(..., CancellationSignal cs) {
+ * try (var csToken = mCancellationSignalSender.beam(cs)) {
+ * remoteIFooService.doCancellableOperation(..., csToken);
+ * }
+ * }
+ * }
+ *
+ * // Receiver:
+ *
+ * class FooManagerService extends IFooService.Stub {
+ * var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver();
+ *
+ * @Override
+ * public void doCancellableOperation(..., IBinder csToken) {
+ * CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken))
+ * // ...
+ * }
+ *
+ * @Override
+ * public void onCancelToken(..., IBinder csToken) {
+ * mCancellationSignalReceiver.cancelToken(csToken))
+ * }
+ *
+ * @Override
+ * public void onForgetToken(..., IBinder csToken) {
+ * mCancellationSignalReceiver.forgetToken(csToken))
+ * }
+ * }
+ *
+ * </pre>
+ *
+ * @hide
+ */
+public class CancellationSignalBeamer {
+
+ static final Cleaner sCleaner = SystemCleaner.cleaner();
+
+ /** The sending side of an {@link CancellationSignalBeamer} */
+ public abstract static class Sender {
+
+ /**
+ * Beams a {@link CancellationSignal} through an existing Binder interface.
+ *
+ * @param cs the {@code CancellationSignal} to beam, or {@code null}.
+ * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em>
+ * the binder call transporting it to the remote process, best with
+ * try-with-resources. {@code null} if {@code cs} was {@code null}.
+ */
+ // TODO(b/254888024): @MustBeClosed
+ @Nullable
+ public CloseableToken beam(@Nullable CancellationSignal cs) {
+ if (cs == null) {
+ return null;
+ }
+ return new Token(this, cs);
+ }
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} was closed.
+ *
+ * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See
+ * {@link CancellationSignalBeamer} for details.
+ */
+ public abstract void onCancel(IBinder token);
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} was GC'd.
+ *
+ * MUST be forwarded to {@link Receiver#forget} with proper ordering. See
+ * {@link CancellationSignalBeamer} for details.
+ */
+ public abstract void onForget(IBinder token);
+
+ private static class Token extends Binder implements CloseableToken, Runnable {
+
+ private final Sender mSender;
+ private Preparer mPreparer;
+
+ private Token(Sender sender, CancellationSignal signal) {
+ mSender = sender;
+ mPreparer = new Preparer(sender, signal, this);
+ }
+
+ @Override
+ public void close() {
+ Preparer preparer = mPreparer;
+ mPreparer = null;
+ if (preparer != null) {
+ preparer.setup();
+ }
+ }
+
+ @Override
+ public void run() {
+ mSender.onForget(this);
+ }
+
+ private static class Preparer implements CancellationSignal.OnCancelListener {
+ private final Sender mSender;
+ private final CancellationSignal mSignal;
+ private final Token mToken;
+
+ private Preparer(Sender sender, CancellationSignal signal, Token token) {
+ mSender = sender;
+ mSignal = signal;
+ mToken = token;
+ }
+
+ void setup() {
+ sCleaner.register(this, mToken);
+ mSignal.setOnCancelListener(this);
+ }
+
+ @Override
+ public void onCancel() {
+ try {
+ mSender.onCancel(mToken);
+ } finally {
+ // Make sure we dispatch onCancel before the cleaner can run.
+ Reference.reachabilityFence(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder.
+ *
+ * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources.
+ */
+ public interface CloseableToken extends IBinder, AutoCloseable {
+ @Override
+ void close(); // No throws
+ }
+ }
+
+ /** The receiving side of a {@link CancellationSignalBeamer}. */
+ public static class Receiver implements IBinder.DeathRecipient {
+ private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>();
+ private final boolean mCancelOnSenderDeath;
+
+ /**
+ * Constructs a new {@code Receiver}.
+ *
+ * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from
+ * {@link #unbeam} are automatically {@link #cancel}led if the sender token
+ * {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
+ * sending process drops all references to the {@link CancellationSignal} before
+ * process death, the cancellation is not guaranteed.
+ */
+ public Receiver(boolean cancelOnSenderDeath) {
+ mCancelOnSenderDeath = cancelOnSenderDeath;
+ }
+
+ /**
+ * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a
+ * {@link CancellationSignal}.
+ *
+ * A subsequent call to {@link #cancel} with the same token will cancel the returned
+ * {@code CancellationSignal}.
+ *
+ * @param token a token that was obtained from {@link Sender}, possibly in a remote process.
+ * @return a {@link CancellationSignal} linked to the given token.
+ */
+ @Nullable
+ public CancellationSignal unbeam(@Nullable IBinder token) {
+ if (token == null) {
+ return null;
+ }
+ synchronized (this) {
+ CancellationSignal cs = mTokenMap.get(token);
+ if (cs != null) {
+ return cs;
+ }
+
+ cs = new CancellationSignal();
+ mTokenMap.put(token, cs);
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ dead(token);
+ }
+ return cs;
+ }
+ }
+
+ /**
+ * Forgets state associated with the given token (if any).
+ *
+ * Subsequent calls to {@link #cancel} or binder death notifications on the token will not
+ * have any effect.
+ *
+ * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and
+ * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+ *
+ * Optionally, the receiving service logic may also invoke this if it can guarantee that
+ * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+ * using the CancellationSignal has been fully completed).
+ *
+ * @param token the token to forget. No-op if {@code null}.
+ */
+ public void forget(@Nullable IBinder token) {
+ synchronized (this) {
+ if (mTokenMap.remove(token) != null) {
+ token.unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ /**
+ * Cancels the {@link CancellationSignal} associated with the given token (if any).
+ *
+ * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and
+ * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+ *
+ * Optionally, the receiving service logic may also invoke this if it can guarantee that
+ * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+ * using the CancellationSignal has been fully completed).
+ *
+ * @param token the token to forget. No-op if {@code null}.
+ */
+ public void cancel(@Nullable IBinder token) {
+ CancellationSignal cs;
+ synchronized (this) {
+ cs = mTokenMap.get(token);
+ if (cs != null) {
+ forget(token);
+ } else {
+ return;
+ }
+ }
+ cs.cancel();
+ }
+
+ private void dead(@NonNull IBinder token) {
+ if (mCancelOnSenderDeath) {
+ cancel(token);
+ } else {
+ forget(token);
+ }
+ }
+
+ @Override
+ public void binderDied(@NonNull IBinder who) {
+ dead(who);
+ }
+
+ @Override
+ public void binderDied() {
+ throw new RuntimeException("unreachable");
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java
index 81b9f22..1ad0424 100644
--- a/core/java/android/service/credentials/BeginGetCredentialOption.java
+++ b/core/java/android/service/credentials/BeginGetCredentialOption.java
@@ -116,12 +116,12 @@
* @param id the unique id associated with this option
* @param type the requested credential type
* @param candidateQueryData the request candidateQueryData
- * @throws IllegalArgumentException If type is empty.
+ * @throws IllegalArgumentException If id or type is empty.
*/
public BeginGetCredentialOption(
@NonNull String id, @NonNull String type,
@NonNull Bundle candidateQueryData) {
- mId = id;
+ mId = Preconditions.checkStringNotEmpty(id, "id must not be empty");
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
Bundle bundle = new Bundle();
bundle.putAll(candidateQueryData);
diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java
index fba91d4..e755581 100644
--- a/core/java/android/service/credentials/CallingAppInfo.java
+++ b/core/java/android/service/credentials/CallingAppInfo.java
@@ -17,6 +17,7 @@
package android.service.credentials;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.SigningInfo;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +33,8 @@
public final class CallingAppInfo implements Parcelable {
@NonNull private final String mPackageName;
@NonNull private final SigningInfo mSigningInfo;
+ @Nullable
+ private final String mOrigin;
/**
* Constructs a new instance.
@@ -41,14 +44,31 @@
*/
public CallingAppInfo(@NonNull String packageName,
@NonNull SigningInfo signingInfo) {
+ this(packageName, signingInfo, /*origin=*/ null);
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param packageName - the package name of the calling app
+ * @param signingInfo - the signing info on the calling app
+ * @param origin - the origin that the calling app wants to use when making request on behalf of
+ * other
+ * @throws IllegalArgumentException If {@code packageName} is null or empty.
+ * @throws NullPointerException If {@code signingInfo} is null.
+ */
+ public CallingAppInfo(@NonNull String packageName,
+ @NonNull SigningInfo signingInfo, @Nullable String origin) {
mPackageName = Preconditions.checkStringNotEmpty(packageName, "package name"
+ "must not be null or empty");
mSigningInfo = Objects.requireNonNull(signingInfo);
+ mOrigin = origin;
}
private CallingAppInfo(@NonNull Parcel in) {
mPackageName = in.readString8();
mSigningInfo = in.readTypedObject(SigningInfo.CREATOR);
+ mOrigin = in.readString8();
}
public static final @NonNull Creator<CallingAppInfo> CREATOR = new Creator<CallingAppInfo>() {
@@ -76,6 +96,22 @@
return mSigningInfo;
}
+ /**
+ * Returns the origin of the calling app if set otherwise returns null.
+ * This value is set only if the origin is different than that of the calling app,
+ * and should be expected from privileged callers(browsers) only when making request on behalf
+ * of other applications.
+ *
+ * Android system makes sure that only applications that poses the permission
+ * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
+ * the incoming {@link android.credentials.GetCredentialRequest} or
+ * {@link android.credentials.CreateCredentialRequest}.
+ */
+ @Nullable
+ public String getOrigin() {
+ return mOrigin;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -85,6 +121,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mPackageName);
dest.writeTypedObject(mSigningInfo, flags);
+ dest.writeString8(mOrigin);
}
@Override
@@ -97,6 +134,7 @@
} else {
builder.append(", mSigningInfo: null");
}
+ builder.append(",mOrigin: " + mOrigin);
builder.append(" }");
return builder.toString();
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 12cd523..f53abce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2198,6 +2198,11 @@
}
mCreated = false;
}
+
+ if (mSurfaceControl != null) {
+ mSurfaceControl.release();
+ mSurfaceControl = null;
+ }
}
private final DisplayListener mDisplayListener = new DisplayListener() {
diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java
index c3ca286..b9ad773 100644
--- a/core/java/android/telephony/CellBroadcastIntents.java
+++ b/core/java/android/telephony/CellBroadcastIntents.java
@@ -104,7 +104,7 @@
* Put the phone ID and sub ID into an intent as extras.
*/
private static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
- int subId = getSubIdForPhone(context, phoneId);
+ int subId = SubscriptionManager.getSubscriptionId(phoneId);
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
intent.putExtra("subscription", subId);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
@@ -112,22 +112,4 @@
intent.putExtra("phone", phoneId);
intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
}
-
- /**
- * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
- * have an active sub
- * @param phoneId the phoneId to use
- * @return the associated sub id
- */
- private static int getSubIdForPhone(Context context, int phoneId) {
- SubscriptionManager subMan =
- (SubscriptionManager) context.getSystemService(
- Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- int[] subIds = subMan.getSubscriptionIds(phoneId);
- if (subIds != null) {
- return subIds[0];
- } else {
- return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- }
- }
}
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 98c0d7f..5aa0f59 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -477,7 +477,14 @@
return mTimeResult;
}
- /** Clears the last received NTP. Intended for use during tests. */
+ /** Sets the last received NTP time. Intended for use during tests. */
+ public void setCachedTimeResult(TimeResult timeResult) {
+ synchronized (this) {
+ mTimeResult = timeResult;
+ }
+ }
+
+ /** Clears the last received NTP time. Intended for use during tests. */
public void clearCachedTimeResult() {
synchronized (this) {
mTimeResult = null;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 20be9d6..5476088 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1261,20 +1261,17 @@
/**
* @hide
- * Returns the display's HDR supported types.
+ * Returns the current mode's supported HDR types.
*
* @see #isHdr()
- * @see HdrCapabilities#getSupportedHdrTypes()
+ * @see Mode#getSupportedHdrTypes()
*/
@TestApi
@NonNull
public int[] getReportedHdrTypes() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.hdrCapabilities == null) {
- return new int[0];
- }
- return mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
+ return mDisplayInfo.getMode().getSupportedHdrTypes();
}
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bdc7333..aef0e65 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -899,9 +899,10 @@
// 3. Get the activity names substring between the indexes
final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
- if (activityStringStartIndex < firstNextSemicolonIndex) {
+ if (activityStringStartIndex >= firstNextSemicolonIndex) {
Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
+ "formatted");
+ return;
}
final String activitySubstring =
denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index db17a53..d84acc0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -25,6 +25,7 @@
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -33,6 +34,7 @@
import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -40,7 +42,6 @@
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
-import com.android.internal.view.IImeTracker;
import com.android.internal.view.IInputMethodManager;
import java.util.ArrayList;
@@ -581,51 +582,57 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */
@AnyThread
- @Nullable
- static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
+ @NonNull
+ static ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
- return null;
+ // Create token with "fake" binder if the service was not found.
+ return new ImeTracker.Token(new Binder(), tag);
}
try {
- return service.onRequestShow(uid, origin, reason);
+ return service.onRequestShow(tag, uid, origin, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */
@AnyThread
- @Nullable
- static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
+ @NonNull
+ static ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
- return null;
+ // Create token with "fake" binder if the service was not found.
+ return new ImeTracker.Token(new Binder(), tag);
}
try {
- return service.onRequestHide(uid, origin, reason);
+ return service.onRequestHide(tag, uid, origin, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */
@AnyThread
- static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
}
try {
- service.onProgress(statsToken, phase);
+ service.onProgress(binder, phase);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */
@AnyThread
- static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -637,8 +644,9 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */
@AnyThread
- static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -650,8 +658,9 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onShown */
@AnyThread
- static void onShown(@NonNull IBinder statsToken) {
+ static void onShown(@NonNull ImeTracker.Token statsToken) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -663,8 +672,9 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */
@AnyThread
- static void onHidden(@NonNull IBinder statsToken) {
+ static void onHidden(@NonNull ImeTracker.Token statsToken) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -676,6 +686,7 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
@AnyThread
@RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
static boolean hasPendingImeVisibilityRequests() {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index e5a99ff..f0d1019 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -26,7 +26,6 @@
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -47,7 +46,7 @@
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
-import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
/** @hide */
@@ -321,9 +320,8 @@
/**
* Creates an IME show request tracking token.
*
- * @param component the component name where the IME show request was created,
- * or {@code null} otherwise
- * (defaulting to {@link ActivityThread#currentProcessName()}).
+ * @param component the name of the component that created the IME request, or {@code null}
+ * otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME show request.
* @param reason the reason why the IME show request was created.
@@ -337,9 +335,8 @@
/**
* Creates an IME hide request tracking token.
*
- * @param component the component name where the IME hide request was created,
- * or {@code null} otherwise
- * (defaulting to {@link ActivityThread#currentProcessName()}).
+ * @param component the name of the component that created the IME request, or {@code null}
+ * otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME hide request.
* @param reason the reason why the IME hide request was created.
@@ -435,8 +432,7 @@
mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
// Update logging flag dynamically.
SystemProperties.addChangeCallback(() ->
- mLogProgress =
- SystemProperties.getBoolean("persist.debug.imetracker", false));
+ mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false));
}
/** Whether progress should be logged. */
@@ -446,10 +442,9 @@
@Override
public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
- IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
- if (binder == null) binder = new Binder();
-
- final Token token = Token.build(binder, component);
+ final var tag = getTag(component);
+ final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin,
+ reason);
Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -461,10 +456,9 @@
@Override
public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
- IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
- if (binder == null) binder = new Binder();
-
- final Token token = Token.build(binder, component);
+ final var tag = getTag(component);
+ final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin,
+ reason);
Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -485,7 +479,7 @@
@Override
public void onFailed(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+ IInputMethodManagerGlobalInvoker.onFailed(token, phase);
Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
}
@@ -499,7 +493,7 @@
@Override
public void onCancelled(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+ IInputMethodManagerGlobalInvoker.onCancelled(token, phase);
Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
}
@@ -507,7 +501,7 @@
@Override
public void onShown(@Nullable Token token) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+ IInputMethodManagerGlobalInvoker.onShown(token);
Log.i(TAG, token.mTag + ": onShown");
}
@@ -515,10 +509,24 @@
@Override
public void onHidden(@Nullable Token token) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+ IInputMethodManagerGlobalInvoker.onHidden(token);
Log.i(TAG, token.mTag + ": onHidden");
}
+
+ /**
+ * Returns a logging tag using the given component name.
+ *
+ * @param component the name of the component that created the IME request, or {@code null}
+ * otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
+ */
+ @NonNull
+ private String getTag(@Nullable String component) {
+ if (component == null) {
+ component = ActivityThread.currentProcessName();
+ }
+ return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+ }
};
/** The singleton IME tracker instance for instrumenting jank metrics. */
@@ -528,28 +536,31 @@
ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
/** A token that tracks the progress of an IME request. */
- class Token implements Parcelable {
+ final class Token implements Parcelable {
+ /** The binder used to identify this token. */
@NonNull
- public final IBinder mBinder;
+ private final IBinder mBinder;
+ /** Logging tag, of the shape "component:random_hexadecimal". */
@NonNull
private final String mTag;
- @NonNull
- private static Token build(@NonNull IBinder binder, @Nullable String component) {
- if (component == null) component = ActivityThread.currentProcessName();
- final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
-
- return new Token(binder, tag);
- }
-
- private Token(@NonNull IBinder binder, @NonNull String tag) {
+ public Token(@NonNull IBinder binder, @NonNull String tag) {
mBinder = binder;
mTag = tag;
}
- /** Returns the {@link Token#mTag} */
+ private Token(@NonNull Parcel in) {
+ mBinder = in.readStrongBinder();
+ mTag = in.readString8();
+ }
+
+ @NonNull
+ public IBinder getBinder() {
+ return mBinder;
+ }
+
@NonNull
public String getTag() {
return mTag;
@@ -562,7 +573,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mBinder);
dest.writeString8(mTag);
}
@@ -571,12 +582,11 @@
public static final Creator<Token> CREATOR = new Creator<>() {
@NonNull
@Override
- public Token createFromParcel(Parcel source) {
- final IBinder binder = source.readStrongBinder();
- final String tag = source.readString8();
- return new Token(binder, tag);
+ public Token createFromParcel(@NonNull Parcel in) {
+ return new Token(in);
}
+ @NonNull
@Override
public Token[] newArray(int size) {
return new Token[size];
@@ -589,40 +599,50 @@
*
* Note: This is held in a separate class so that it only gets initialized when actually needed.
*/
- class Debug {
+ final class Debug {
+ @NonNull
private static final Map<Integer, String> sTypes =
getFieldMapping(ImeTracker.class, "TYPE_");
+ @NonNull
private static final Map<Integer, String> sStatus =
getFieldMapping(ImeTracker.class, "STATUS_");
+ @NonNull
private static final Map<Integer, String> sOrigins =
getFieldMapping(ImeTracker.class, "ORIGIN_");
+ @NonNull
private static final Map<Integer, String> sPhases =
getFieldMapping(ImeTracker.class, "PHASE_");
+ @NonNull
public static String typeToString(@Type int type) {
return sTypes.getOrDefault(type, "TYPE_" + type);
}
+ @NonNull
public static String statusToString(@Status int status) {
return sStatus.getOrDefault(status, "STATUS_" + status);
}
+ @NonNull
public static String originToString(@Origin int origin) {
return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
}
+ @NonNull
public static String phaseToString(@Phase int phase) {
return sPhases.getOrDefault(phase, "PHASE_" + phase);
}
- private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+ @NonNull
+ private static Map<Integer, String> getFieldMapping(Class<?> cls,
+ @NonNull String fieldPrefix) {
return Arrays.stream(cls.getDeclaredFields())
.filter(field -> field.getName().startsWith(fieldPrefix))
.collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
}
- private static int getFieldValue(Field field) {
+ private static int getFieldValue(@NonNull Field field) {
try {
return field.getInt(null);
} catch (IllegalAccessException e) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b89dd31..9f9a781 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -52,6 +52,7 @@
import android.graphics.RenderNode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
@@ -3238,6 +3239,44 @@
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
mPreserveSelection = true;
+
+ // No-op for the old context menu because it doesn't have icons.
+ adjustIconSpacing(menu);
+ }
+
+ /**
+ * Adjust icon spacing to align the texts.
+ * @hide
+ */
+ @VisibleForTesting
+ public void adjustIconSpacing(ContextMenu menu) {
+ int width = -1;
+ int height = -1;
+ for (int i = 0; i < menu.size(); ++i) {
+ final MenuItem item = menu.getItem(i);
+ final Drawable d = item.getIcon();
+ if (d == null) {
+ continue;
+ }
+
+ width = Math.max(width, d.getIntrinsicWidth());
+ height = Math.max(height, d.getIntrinsicHeight());
+ }
+
+ if (width < 0 || height < 0) {
+ return; // No menu has icon drawable.
+ }
+
+ GradientDrawable paddingDrawable = new GradientDrawable();
+ paddingDrawable.setSize(width, height);
+
+ for (int i = 0; i < menu.size(); ++i) {
+ final MenuItem item = menu.getItem(i);
+ final Drawable d = item.getIcon();
+ if (d == null) {
+ item.setIcon(paddingDrawable);
+ }
+ }
}
@Nullable
diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
similarity index 71%
rename from core/java/com/android/internal/view/IImeTracker.aidl
rename to core/java/com/android/internal/inputmethod/IImeTracker.aidl
index b062ca7..c7418ee 100644
--- a/core/java/com/android/internal/view/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -14,43 +14,45 @@
* limitations under the License.
*/
-package com.android.internal.view;
+package com.android.internal.inputmethod;
import android.view.inputmethod.ImeTracker;
/**
- * Interface to the global Ime tracker, used by all client applications.
+ * Interface to the global IME tracker service, used by all client applications.
* {@hide}
*/
interface IImeTracker {
/**
- * Called when an IME show request is created,
- * returns a new Binder to be associated with the IME tracking token.
+ * Called when an IME show request is created.
*
+ * @param tag the logging tag.
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME show request.
* @param reason the reason why the IME show request was created.
+ * @return A new IME tracking token.
*/
- IBinder onRequestShow(int uid, int origin, int reason);
+ ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason);
/**
- * Called when an IME hide request is created,
- * returns a new Binder to be associated with the IME tracking token.
+ * Called when an IME hide request is created.
*
+ * @param tag the logging tag.
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME hide request.
* @param reason the reason why the IME hide request was created.
+ * @return A new IME tracking token.
*/
- IBinder onRequestHide(int uid, int origin, int reason);
+ ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason);
/**
* Called when the IME request progresses to a further phase.
*
- * @param statsToken the token tracking the current IME request.
+ * @param binder the binder of token tracking the current IME request.
* @param phase the new phase the IME request reached.
*/
- oneway void onProgress(in IBinder statsToken, int phase);
+ oneway void onProgress(in IBinder binder, int phase);
/**
* Called when the IME request fails.
@@ -58,7 +60,7 @@
* @param statsToken the token tracking the current IME request.
* @param phase the phase the IME request failed at.
*/
- oneway void onFailed(in IBinder statsToken, int phase);
+ oneway void onFailed(in ImeTracker.Token statsToken, int phase);
/**
* Called when the IME request is cancelled.
@@ -66,21 +68,21 @@
* @param statsToken the token tracking the current IME request.
* @param phase the phase the IME request was cancelled at.
*/
- oneway void onCancelled(in IBinder statsToken, int phase);
+ oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
/**
* Called when the IME show request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
- oneway void onShown(in IBinder statsToken);
+ oneway void onShown(in ImeTracker.Token statsToken);
/**
* Called when the IME hide request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
- oneway void onHidden(in IBinder statsToken);
+ oneway void onHidden(in ImeTracker.Token statsToken);
/**
* Checks whether there are any pending IME visibility requests.
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 5805d0e..9a4610e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -23,11 +23,11 @@
import android.view.inputmethod.EditorInfo;
import android.window.ImeOnBackInvokedDispatcher;
+import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.view.IImeTracker;
/**
* Public interface to the global input method manager, used by all client
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d2ee5de..1bbe8ee 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1714,7 +1714,7 @@
this type.
-->
<flag name="remoteMessaging" value="0x200" />
- <!-- The system exmpted foreground service use cases.
+ <!-- The system exempted foreground service use cases.
<p>Requires the app to hold the permission
{@link android.Manifest.permission#FOREGROUND_SERVICE_SYSTEM_EXEMPTED} in order to use
this type. Apps are allowed to use this type only in the use cases listed in
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 12eff67..2f94ed7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3065,6 +3065,10 @@
<string name="config_credentialManagerDialogComponent" translatable="false"
>com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
+ <!-- Name of the broadcast receiver that is used to receive provider change events -->
+ <string name="config_credentialManagerReceiverComponent" translatable="false"
+ >com.android.credentialmanager/com.android.credentialmanager.CredentialProviderReceiver</string>
+
<!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
<string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e89fc8..12646a0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2210,6 +2210,7 @@
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
<java-symbol type="string" name="config_credentialManagerDialogComponent" />
+ <java-symbol type="string" name="config_credentialManagerReceiverComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
<java-symbol type="string" name="config_deviceConfiguratorPackageName" />
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 55ef854..980211f 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
+import android.util.PollingCheck;
import android.view.View;
import android.widget.TextView;
@@ -91,6 +92,9 @@
var densityRef = new AtomicReference<Float>();
scenario.onActivity(activity -> {
+ assertThat(activity.getResources().getConfiguration().fontScale)
+ .isWithin(0.05f)
+ .of(2f);
densityRef.compareAndSet(null, activity.getResources().getDisplayMetrics().density);
});
var density = densityRef.get();
@@ -141,6 +145,15 @@
fontScale
);
});
+
+ PollingCheck.waitFor(/* timeout= */ 5000, () ->
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .getResources()
+ .getConfiguration()
+ .fontScale == fontScale
+ );
}
private Matcher<View> withTextSizeInRange(float sizeStartPx, float sizeEndPx) {
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 249e246..ef106bc 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -90,11 +90,7 @@
}
.forEach { (table, sp) ->
try {
- assertWithMessage(
- "convertSpToDp(%s) on table: %s",
- sp.toString(),
- table.toString()
- )
+ assertWithMessage("convertSpToDp(%s) on table: %s", sp, table)
.that(table.convertSpToDp(sp))
.isFinite()
} catch (e: Exception) {
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index b533b47..25664fb 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -101,8 +101,11 @@
mGetRequest = new GetCredentialRequest.Builder(Bundle.EMPTY).addCredentialOption(
new CredentialOption(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY,
Bundle.EMPTY, false)).build();
- mCreateRequest = new CreateCredentialRequest(Credential.TYPE_PASSWORD_CREDENTIAL,
- Bundle.EMPTY, Bundle.EMPTY, false, false);
+ mCreateRequest = new CreateCredentialRequest.Builder(Bundle.EMPTY, Bundle.EMPTY)
+ .setType(Credential.TYPE_PASSWORD_CREDENTIAL)
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(false)
+ .build();
mClearRequest = new ClearCredentialStateRequest(Bundle.EMPTY);
final Slice slice = new Slice.Builder(Uri.parse("foo://bar"), null).addText("some text",
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
new file mode 100644
index 0000000..42c97f3
--- /dev/null
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.os;
+
+import static android.os.CancellationSignalBeamer.Sender;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.CancellationSignalBeamer.Receiver;
+import android.util.PollingCheck;
+import android.util.PollingCheck.PollingCheckCondition;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CancellationSignalBeamerTest {
+
+ private CancellationSignal mSenderSignal = new CancellationSignal();
+ private CancellationSignal mReceivedSignal;
+ private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Test
+ public void testBeam_null() {
+ try (var token = mSender.beam(null)) {
+ assertThat(token).isNull();
+ invokeGenericService(token);
+ }
+ assertThat(mReceivedSignal).isNull();
+ }
+
+ @Test
+ public void testBeam_nonNull() {
+ try (var token = mSender.beam(mSenderSignal)) {
+ assertThat(token).isNotNull();
+ invokeGenericService(token);
+ }
+ assertThat(mReceivedSignal).isNotNull();
+ }
+
+ @Test
+ public void testBeam_async() {
+ IBinder outerToken;
+ try (var token = mSender.beam(mSenderSignal)) {
+ assertThat(token).isNotNull();
+ outerToken = token;
+ }
+ invokeGenericService(outerToken);
+ assertThat(mReceivedSignal).isNotNull();
+ }
+
+ @Test
+ public void testCancelOnSentSignal_cancelsReceivedSignal() {
+ try (var token = mSender.beam(mSenderSignal)) {
+ invokeGenericService(token);
+ }
+ mSenderSignal.cancel();
+ assertThat(mReceivedSignal.isCanceled()).isTrue();
+ }
+
+ @Test
+ public void testSendingCancelledSignal_cancelsReceivedSignal() {
+ mSenderSignal.cancel();
+ try (var token = mSender.beam(mSenderSignal)) {
+ invokeGenericService(token);
+ }
+ assertThat(mReceivedSignal.isCanceled()).isTrue();
+ }
+
+ @Test
+ public void testUnbeam_null() {
+ assertThat(mReceiver.unbeam(null)).isNull();
+ }
+
+ @Test
+ public void testForget_null() {
+ mReceiver.forget(null);
+ }
+
+ @Test
+ public void testCancel_null() {
+ mReceiver.cancel(null);
+ }
+
+ @Test
+ public void testForget_withUnknownToken() {
+ mReceiver.forget(new Binder());
+ }
+
+ @Test
+ public void testCancel_withUnknownToken() {
+ mReceiver.cancel(new Binder());
+ }
+
+ @Test
+ public void testBinderDied_withUnknownToken() {
+ mReceiver.binderDied(new Binder());
+ }
+
+ @Test
+ public void testReceiverWithCancelOnSenderDead_cancelsOnSenderDeath() {
+ var receiver = new Receiver(true /* cancelOnSenderDeath */);
+ var token = new Binder();
+ var signal = receiver.unbeam(token);
+ receiver.binderDied(token);
+ assertThat(signal.isCanceled()).isTrue();
+ }
+
+ @Test
+ public void testReceiverWithoutCancelOnSenderDead_doesntCancelOnSenderDeath() {
+ var receiver = new Receiver(false /* cancelOnSenderDeath */);
+ var token = new Binder();
+ var signal = receiver.unbeam(token);
+ receiver.binderDied(token);
+ assertThat(signal.isCanceled()).isFalse();
+ }
+
+ @Test
+ public void testDroppingSentSignal_dropsReceivedSignal() throws Exception {
+ // In a multiprocess scenario, sending token over Binder might leak the token
+ // on both ends if we create a reference cycle. Simulate that worst-case scenario
+ // here by leaking it directly, then test that cleanup of the signals still works.
+ var receivedSignalCleaned = new CountDownLatch(1);
+ var tokenRef = new Object[1];
+ // Reference the cancellation signals in a separate method scope, so we don't
+ // accidentally leak them on the stack / in a register.
+ Runnable r = () -> {
+ try (var token = mSender.beam(mSenderSignal)) {
+ tokenRef[0] = token;
+ invokeGenericService(token);
+ }
+ mSenderSignal = null;
+
+ Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+ mReceivedSignal = null;
+ };
+ r.run();
+
+ waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+ Reference.reachabilityFence(tokenRef[0]);
+ }
+
+ @Test
+ public void testRepeatedBeaming_doesntLeak() throws Exception {
+ var receivedSignalCleaned = new CountDownLatch(1);
+ var tokenRef = new Object[1];
+ // Reference the cancellation signals in a separate method scope, so we don't
+ // accidentally leak them on the stack / in a register.
+ Runnable r = () -> {
+ try (var token = mSender.beam(mSenderSignal)) {
+ tokenRef[0] = token;
+ invokeGenericService(token);
+ }
+ // Beaming again leaves mReceivedSignal dangling, so it should be collected.
+ mSender.beam(mSenderSignal).close();
+
+ Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+ mReceivedSignal = null;
+ };
+ r.run();
+
+ waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+ Reference.reachabilityFence(tokenRef[0]);
+ }
+
+ private void waitForWithGc(PollingCheckCondition condition) throws IOException {
+ try {
+ PollingCheck.waitFor(() -> {
+ Runtime.getRuntime().gc();
+ return condition.canProceed();
+ });
+ } catch (AssertionError e) {
+ File heap = new File(mContext.getExternalFilesDir(null), "dump.hprof");
+ Debug.dumpHprofData(heap.getAbsolutePath());
+ throw e;
+ }
+ }
+
+ private void invokeGenericService(IBinder cancellationSignalToken) {
+ mReceivedSignal = mReceiver.unbeam(cancellationSignalToken);
+ }
+
+ private final Sender mSender = new Sender() {
+ @Override
+ public void onCancel(IBinder token) {
+ mReceiver.cancel(token);
+ }
+
+ @Override
+ public void onForget(IBinder token) {
+ mReceiver.forget(token);
+ }
+ };
+
+ private final Receiver mReceiver = new Receiver(false);
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 0c7550e..777246b 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -33,6 +33,8 @@
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.view.ContextMenu;
import android.view.MenuItem;
@@ -167,4 +169,75 @@
assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST);
assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE);
}
+
+ @UiThreadTest
+ @Test
+ public void testAdjustIconSpaces() {
+ GradientDrawable gd = new GradientDrawable();
+ gd.setSize(128, 256);
+
+ // Setup mocks
+ ContextMenu menu = mock(ContextMenu.class);
+
+ MenuItem mockIconMenu = newMockMenuItem();
+ when(mockIconMenu.getIcon()).thenReturn(gd);
+
+ MenuItem mockNoIconMenu = newMockMenuItem();
+ when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+ MenuItem mockNoIconMenu2 = newMockMenuItem();
+ when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+ when(menu.size()).thenReturn(3);
+ when(menu.getItem(0)).thenReturn(mockIconMenu);
+ when(menu.getItem(1)).thenReturn(mockNoIconMenu);
+ when(menu.getItem(2)).thenReturn(mockNoIconMenu2);
+
+
+ // Execute the test method
+ EditText et = mActivity.findViewById(R.id.editText);
+ Editor editor = et.getEditorForTesting();
+ editor.adjustIconSpacing(menu);
+
+ // Verify
+ ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class);
+ verify(mockNoIconMenu).setIcon(drawableCaptor.capture());
+
+ Drawable paddingDrawable = drawableCaptor.getValue();
+ assertThat(paddingDrawable).isNotNull();
+ assertThat(paddingDrawable.getIntrinsicWidth()).isEqualTo(128);
+ assertThat(paddingDrawable.getIntrinsicHeight()).isEqualTo(256);
+
+ ArgumentCaptor<Drawable> drawableCaptor2 = ArgumentCaptor.forClass(Drawable.class);
+ verify(mockNoIconMenu2).setIcon(drawableCaptor2.capture());
+
+ Drawable paddingDrawable2 = drawableCaptor2.getValue();
+ assertThat(paddingDrawable2).isSameInstanceAs(paddingDrawable);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testAdjustIconSpacesNoIconCase() {
+ // Setup mocks
+ ContextMenu menu = mock(ContextMenu.class);
+
+ MenuItem mockNoIconMenu = newMockMenuItem();
+ when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+ MenuItem mockNoIconMenu2 = newMockMenuItem();
+ when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+ when(menu.size()).thenReturn(2);
+ when(menu.getItem(0)).thenReturn(mockNoIconMenu);
+ when(menu.getItem(1)).thenReturn(mockNoIconMenu2);
+
+ // Execute the test method
+ EditText et = mActivity.findViewById(R.id.editText);
+ Editor editor = et.getEditorForTesting();
+ editor.adjustIconSpacing(menu);
+
+ // Verify
+ verify(mockNoIconMenu, times(0)).setIcon(any());
+ verify(mockNoIconMenu2, times(0)).setIcon(any());
+ }
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 9ccf3b3..3b8b8c7 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -117,6 +117,31 @@
}
@Test
+ public void testGetReportedHdrTypes_returns_mode_specific_hdr_types() {
+ setDisplayInfoPortrait(mDisplayInfo);
+ float[] alternativeRefreshRates = new float[0];
+ int[] hdrTypesWithDv = new int[] {1, 2, 3, 4};
+ Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f,
+ alternativeRefreshRates, hdrTypesWithDv);
+
+ int[] hdrTypesWithoutDv = new int[]{2, 3, 4};
+ Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f,
+ alternativeRefreshRates, hdrTypesWithoutDv);
+
+ mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv};
+ mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0);
+
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+
+ mDisplayInfo.modeId = 0;
+ assertArrayEquals(hdrTypesWithDv, display.getReportedHdrTypes());
+
+ mDisplayInfo.modeId = 1;
+ assertArrayEquals(hdrTypesWithoutDv, display.getReportedHdrTypes());
+ }
+
+ @Test
public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
setDisplayInfoPortrait(mDisplayInfo);
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 16fdffa..f3318f4 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -122,6 +122,7 @@
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
<permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
<permission name="android.permission.BIND_IMS_SERVICE"/>
+ <permission name="android.permission.BIND_SATELLITE_SERVICE"/>
<permission name="android.permission.BIND_TELEPHONY_DATA_SERVICE"/>
<permission name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"/>
<permission name="android.permission.CALL_PRIVILEGED"/>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index acf17e6..b3fff1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -29,8 +29,9 @@
import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -39,7 +40,6 @@
import android.util.Log;
import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
-import android.view.IWindowFocusObserver;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -121,23 +121,22 @@
private IOnBackInvokedCallback mActiveCallback;
@VisibleForTesting
- final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
- @Override
- public void focusGained(IBinder inputToken) { }
- @Override
- public void focusLost(IBinder inputToken) {
- mShellExecutor.execute(() -> {
- if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
- // If an uninterruptible animation is already in progress, we should ignore
- // this due to it may cause focus lost. (alpha = 0)
- return;
+ final RemoteCallback mNavigationObserver = new RemoteCallback(
+ new RemoteCallback.OnResultListener() {
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ mShellExecutor.execute(() -> {
+ if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+ // If an uninterruptible animation is already in progress, we should
+ // ignore this due to it may cause focus lost. (alpha = 0)
+ return;
+ }
+ ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
+ setTriggerBack(false);
+ onGestureFinished(false);
+ });
}
- ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
- setTriggerBack(false);
- onGestureFinished(false);
});
- }
- };
private final BackAnimationBackground mAnimationBackground;
@@ -351,7 +350,7 @@
try {
mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
- mFocusObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
+ mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
onBackNavigationInfoReceived(mBackNavigationInfo);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f616e6f..8a18271 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1091,7 +1091,7 @@
// ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
// because DividerView won't receive onImeVisibilityChanged callback after it being
// re-inflated.
- mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus,
+ mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus || isFloating,
"onImeStartPositioning");
return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 26f47fc..d094c22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -108,6 +108,10 @@
}
if (!createdWindowDecor) {
mSyncQueue.runInSync(t -> {
+ if (!leash.isValid()) {
+ // Task vanished before sync completion
+ return;
+ }
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
t.setWindowCrop(leash, null);
@@ -136,6 +140,10 @@
final Point positionInParent = state.mTaskInfo.positionInParent;
if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
mSyncQueue.runInSync(t -> {
+ if (!state.mLeash.isValid()) {
+ // Task vanished before sync completion
+ return;
+ }
t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 2981f5e..9224b3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -174,7 +174,7 @@
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final TaskPositioner taskPositioner =
- new TaskPositioner(mTaskOrganizer, windowDecoration);
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 75bc985..766c4cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -558,7 +558,8 @@
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final TaskPositioner taskPositioner =
- new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
+ mDragStartListener);
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index d3f9227..a3d364a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -19,9 +19,11 @@
import android.annotation.IntDef;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.util.DisplayMetrics;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
class TaskPositioner implements DragPositioningCallback {
@@ -35,6 +37,7 @@
static final int CTRL_TYPE_BOTTOM = 8;
private final ShellTaskOrganizer mTaskOrganizer;
+ private final DisplayController mDisplayController;
private final WindowDecoration mWindowDecoration;
private final Rect mTaskBoundsAtDragStart = new Rect();
@@ -45,14 +48,16 @@
private int mCtrlType;
private DragStartListener mDragStartListener;
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
- this(taskOrganizer, windowDecoration, dragStartListener -> {});
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DisplayController displayController) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {});
}
TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DragStartListener dragStartListener) {
+ DisplayController displayController, DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
+ mDisplayController = displayController;
mDragStartListener = dragStartListener;
}
@@ -128,15 +133,44 @@
mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
}
+ // If width or height are negative or less than the minimum width or height, revert the
+ // respective bounds to use previous bound dimensions.
+ if (mRepositionTaskBounds.width() < getMinWidth()) {
+ mRepositionTaskBounds.right = oldRight;
+ mRepositionTaskBounds.left = oldLeft;
+ }
+ if (mRepositionTaskBounds.height() < getMinHeight()) {
+ mRepositionTaskBounds.top = oldTop;
+ mRepositionTaskBounds.bottom = oldBottom;
+ }
+ // If there are no changes to the bounds after checking new bounds against minimum width
+ // and height, do not set bounds and return false
if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
&& oldRight == mRepositionTaskBounds.right
&& oldBottom == mRepositionTaskBounds.bottom) {
return false;
}
+
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
return true;
}
+ private float getMinWidth() {
+ return mWindowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize()
+ : mWindowDecoration.mTaskInfo.minWidth;
+ }
+
+ private float getMinHeight() {
+ return mWindowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize()
+ : mWindowDecoration.mTaskInfo.minHeight;
+ }
+
+ private float getDefaultMinSize() {
+ float density = mDisplayController.getDisplayLayout(mWindowDecoration.mTaskInfo.displayId)
+ .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return mWindowDecoration.mTaskInfo.defaultMinSize * density;
+ }
+
interface DragStartListener {
/**
* Inform the implementing class that a drag resize has started
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 8a5b490..5a4a44f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -40,7 +39,6 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.provider.Settings;
@@ -341,8 +339,7 @@
mController.setTriggerBack(true); // Fake trigger back
// In case the focus has been changed.
- IBinder token = mock(IBinder.class);
- mController.mFocusObserver.focusLost(token);
+ mController.mNavigationObserver.sendResult(null);
mShellExecutor.flushAll();
verify(mAnimatorCallback).onBackCancelled();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index f185a8a..8f66f4e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -8,6 +8,8 @@
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
import androidx.test.filters.SmallTest
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
@@ -45,6 +47,11 @@
@Mock
private lateinit var taskBinder: IBinder
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockDisplayLayout: DisplayLayout
+
private lateinit var taskPositioner: TaskPositioner
@Before
@@ -54,12 +61,21 @@
taskPositioner = TaskPositioner(
mockShellTaskOrganizer,
mockWindowDecoration,
+ mockDisplayController,
mockDragStartListener
)
+
`when`(taskToken.asBinder()).thenReturn(taskBinder)
+ `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
}
@@ -209,8 +225,239 @@
})
}
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to width of 95px and height of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 95
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.top ==
+ STARTING_BOUNDS.top &&
+ change.configuration.windowConfiguration.bounds.bottom ==
+ STARTING_BOUNDS.bottom
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to height of 95px and width of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 95
+ val newY = STARTING_BOUNDS.top.toFloat() + 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.right ==
+ STARTING_BOUNDS.right &&
+ change.configuration.windowConfiguration.bounds.left ==
+ STARTING_BOUNDS.left
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to height of -5px and width of 95px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 105
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.top ==
+ STARTING_BOUNDS.top &&
+ change.configuration.windowConfiguration.bounds.bottom ==
+ STARTING_BOUNDS.bottom
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to width of -5px and height of 95px
+ val newX = STARTING_BOUNDS.right.toFloat() - 105
+ val newY = STARTING_BOUNDS.top.toFloat() + 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.right ==
+ STARTING_BOUNDS.right &&
+ change.configuration.windowConfiguration.bounds.left ==
+ STARTING_BOUNDS.left
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to height 20px and width 20px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 80
+ val newY = STARTING_BOUNDS.top.toFloat() + 80
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to height 5px and width 5px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 95
+ val newY = STARTING_BOUNDS.top.toFloat() + 95
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_useDefaultMinWhenMinWidthInvalid() {
+ mockWindowDecoration.mTaskInfo.minWidth = -1
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to width and height of 3px with invalid minWidth = -1 and defaultMinSize = 5px
+ val newX = STARTING_BOUNDS.right.toFloat() - 97
+ val newY = STARTING_BOUNDS.top.toFloat() + 97
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_useMinWidthWhenValid() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to width and height of 7px with valid minWidth = 10px and defaultMinSize = 5px
+ val newX = STARTING_BOUNDS.right.toFloat() - 93
+ val newY = STARTING_BOUNDS.top.toFloat() + 93
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
companion object {
private const val TASK_ID = 5
+ private const val MIN_WIDTH = 10
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ private const val DEFAULT_MIN = 40
+ private const val DISPLAY_ID = 1
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
}
}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 0b08681..faa7f7f 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -3049,7 +3049,13 @@
/**
- * Gets the {@link LogSessionId}.
+ * Sets the {@link LogSessionId}.
+ *
+ * <p>The implementation of this method varies by DRM provider; Please refer
+ * to your DRM provider documentation for more details on this method.
+ *
+ * @throws UnsupportedOperationException when the vendor plugin does not
+ * implement this method
*/
public void setLogSessionId(@NonNull LogSessionId logSessionId) {
Objects.requireNonNull(logSessionId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index afc2bb1..6eed483 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -190,9 +190,7 @@
break;
}
case DO_SEND_TIME_SHIFT_MODE: {
- SomeArgs args = (SomeArgs) msg.obj;
- mSessionImpl.sendTimeShiftMode(args.argi1);
- args.recycle();
+ mSessionImpl.sendTimeShiftMode((Integer) msg.obj);
break;
}
case DO_SEND_AVAILABLE_SPEEDS: {
@@ -447,7 +445,7 @@
@Override
public void sendTimeShiftMode(int mode) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_SEND_TIME_SHIFT_MODE, mode));
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SEND_TIME_SHIFT_MODE, mode));
}
@Override
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index b36cb5c..dfc8aa0 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -41,6 +41,15 @@
android:excludeFromRecents="true"
android:theme="@style/Theme.CredentialSelector">
</activity>
+
+ <receiver
+ android:name=".CredentialProviderReceiver"
+ android:exported="true"
+ android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
+ <intent-filter>
+ <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0d25bec..30b97bf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -73,7 +73,7 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testCreatePasskeyRequestInfo()
+ ) ?: testCreatePasswordRequestInfo()
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
@@ -402,30 +402,26 @@
)
val credentialData = request.credentialData
return RequestInfo.newCreateRequestInfo(
- Binder(),
- CreateCredentialRequest(
- "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
- credentialData,
- /*candidateQueryData=*/ Bundle(),
- /*isSystemProviderRequired=*/ false,
- /*alwaysSendAppInfoToProvider=*/ true
- ),
- "com.google.android.youtube"
+ Binder(),
+ CreateCredentialRequest.Builder(credentialData, Bundle())
+ .setType("androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL")
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(true)
+ .build(),
+ "com.google.android.youtube"
)
}
private fun testCreatePasswordRequestInfo(): RequestInfo {
val request = CreatePasswordRequest("beckett-bakert@gmail.com", "password123")
return RequestInfo.newCreateRequestInfo(
- Binder(),
- CreateCredentialRequest(
- TYPE_PASSWORD_CREDENTIAL,
- request.credentialData,
- request.candidateQueryData,
- /*isSystemProviderRequired=*/ false,
- /*alwaysSendAppInfoToProvider=*/ true
- ),
- "com.google.android.youtube"
+ Binder(),
+ CreateCredentialRequest.Builder(request.credentialData, request.candidateQueryData)
+ .setType(TYPE_PASSWORD_CREDENTIAL)
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(true)
+ .build(),
+ "com.google.android.youtube"
)
}
@@ -437,15 +433,13 @@
displayInfo.toBundle()
)
return RequestInfo.newCreateRequestInfo(
- Binder(),
- CreateCredentialRequest(
- "other-sign-ins",
- data,
- /*candidateQueryData=*/ Bundle(),
- /*isSystemProviderRequired=*/ false,
- /*alwaysSendAppInfoToProvider=*/ true
- ),
- "com.google.android.youtube"
+ Binder(),
+ CreateCredentialRequest.Builder(data, Bundle())
+ .setType("other-sign-ins")
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(true)
+ .build(),
+ "com.google.android.youtube"
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
new file mode 100644
index 0000000..ee8cffe
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.credentialmanager.common.Constants
+
+
+class CredentialProviderReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
+
+ val sharedPreferences = context?.getSharedPreferences(context?.packageName,
+ Context.MODE_PRIVATE)
+ sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 8ba1ff3..37b8ae0 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -76,7 +76,10 @@
android:layout_height="48dp"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
- android:padding="8dp" />
+ android:padding="8dp"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
</com.android.systemui.statusbar.notification.row.AppControlView>
<!-- ChannelRows get added dynamically -->
@@ -101,7 +104,7 @@
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
- style="@style/TextAppearance.NotificationInfo.Button"/>
+ style="@style/Widget.Dialog.Button"/>
<TextView
android:id="@+id/done_button"
android:text="@string/inline_ok_button"
@@ -113,7 +116,7 @@
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:layout_alignParentEnd="true"
- style="@style/TextAppearance.NotificationInfo.Button"/>
+ style="@style/Widget.Dialog.Button"/>
</RelativeLayout>
</LinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index d03cd7e..190f999 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -85,6 +85,9 @@
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"
/>
</LinearLayout>
</com.android.systemui.statusbar.notification.row.ChannelRow>
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f32ea71..ab78b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -155,7 +155,7 @@
// TODO(b/255618149): Tracking Bug
@JvmField
val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
- unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
+ releasedFlag(216, "customizable_lock_screen_quick_affordances")
/** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
// TODO(b/256513609): Tracking Bug
@@ -187,7 +187,7 @@
// TODO(b/262780002): Tracking Bug
@JvmField
- val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true)
+ val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
/** A different path for unocclusion transitions back to keyguard */
// TODO(b/262859270): Tracking Bug
@@ -214,10 +214,9 @@
// TODO(b/266242192): Tracking Bug
@JvmField
val LOCK_SCREEN_LONG_PRESS_ENABLED =
- unreleasedFlag(
+ releasedFlag(
228,
- "lock_screen_long_press_enabled",
- teamfood = true,
+ "lock_screen_long_press_enabled"
)
// 300 - power menu
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index f0b221d..0de3246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -344,7 +344,7 @@
or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
}
-class ChannelEditorDialog(context: Context) : Dialog(context) {
+class ChannelEditorDialog(context: Context, theme: Int) : Dialog(context, theme) {
fun updateDoneButtonText(hasChanges: Boolean) {
findViewById<TextView>(R.id.done_button)?.setText(
if (hasChanges)
@@ -361,7 +361,7 @@
}
fun build(): ChannelEditorDialog {
- return ChannelEditorDialog(context)
+ return ChannelEditorDialog(context, R.style.Theme_SystemUI_Dialog)
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 0926f8a..a359216 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -142,7 +142,7 @@
protected final SystemSupport mSystemSupport;
protected final WindowManagerInternal mWindowManagerService;
private final SystemActionPerformer mSystemActionPerformer;
- private final AccessibilityWindowManager mA11yWindowManager;
+ final AccessibilityWindowManager mA11yWindowManager;
private final DisplayManager mDisplayManager;
private final PowerManager mPowerManager;
private final IPlatformCompat mIPlatformCompat;
@@ -2006,15 +2006,14 @@
return accessibilityWindowId;
}
- private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+ int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType);
// If the caller is a proxy and the found window doesn't belong to a proxy display
// (or vice versa), then return null. This doesn't work if there are multiple active
- // proxys, but in the future this code shouldn't be needed if input and a11y focus are
+ // proxys, but in the future this code shouldn't be needed if input focus are
// properly split. (so we will deal with the issues if we see them).
- //TODO(254545943): Remove this when there is user and proxy separation of input and a11y
- // focus
+ //TODO(254545943): Remove this when there is user and proxy separation of input focus
if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index cde820a..1f8a779 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3823,7 +3823,7 @@
try {
mProxyManager.registerProxy(client, displayId, mContext,
sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
- mWindowManagerService, mA11yWindowManager);
+ mWindowManagerService);
synchronized (mLock) {
notifyClearAccessibilityCacheLocked();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index f0c6c4f..094053e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -19,6 +19,7 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
@@ -104,6 +105,8 @@
private boolean mTouchInteractionInProgress;
+ private boolean mHasProxy;
+
/** List of Display Windows Observer, mapping from displayId -> DisplayWindowsObserver. */
private final SparseArray<DisplayWindowsObserver> mDisplayWindowsObservers =
new SparseArray<>();
@@ -159,6 +162,9 @@
* Returns {@code true} if the window belongs to a display of {@code displayTypes}.
*/
public boolean windowIdBelongsToDisplayType(int focusedWindowId, int displayTypes) {
+ if (!mHasProxy) {
+ return true;
+ }
// UIAutomation wants focus from any display type.
final int displayTypeMask = DISPLAY_TYPE_PROXY | DISPLAY_TYPE_DEFAULT;
if ((displayTypes & displayTypeMask) == displayTypeMask) {
@@ -195,6 +201,7 @@
private List<AccessibilityWindowInfo> mWindows;
private boolean mTrackingWindows = false;
private boolean mHasWatchOutsideTouchWindow;
+ private int mProxyDisplayAccessibilityFocusedWindow;
private boolean mIsProxy;
/**
@@ -608,8 +615,11 @@
final int windowCount = windows.size();
final boolean isTopFocusedDisplay = mDisplayId == mTopFocusedDisplayId;
+ // A proxy with an a11y-focused window is a11y-focused should use the proxy focus id.
final boolean isAccessibilityFocusedDisplay =
- mDisplayId == mAccessibilityFocusedDisplayId;
+ mDisplayId == mAccessibilityFocusedDisplayId
+ || (mIsProxy && mProxyDisplayAccessibilityFocusedWindow
+ != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
// Modifies the value of top focused window, active window and a11y focused window
// only if this display is top focused display which has the top focused window.
if (isTopFocusedDisplay) {
@@ -635,9 +645,12 @@
// We'll clear accessibility focus if the window with focus is no longer visible to
// accessibility services.
+ int a11yFocusedWindowId = mIsProxy
+ ? mProxyDisplayAccessibilityFocusedWindow
+ : mAccessibilityFocusedWindowId;
if (isAccessibilityFocusedDisplay) {
- shouldClearAccessibilityFocus = mAccessibilityFocusedWindowId
- != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ shouldClearAccessibilityFocus = a11yFocusedWindowId
+ != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
boolean hasWindowIgnore = false;
@@ -701,7 +714,7 @@
if (isAccessibilityFocusedDisplay) {
for (int i = 0; i < accessibilityWindowCount; i++) {
final AccessibilityWindowInfo window = mWindows.get(i);
- if (window.getId() == mAccessibilityFocusedWindowId) {
+ if (window.getId() == a11yFocusedWindowId) {
window.setAccessibilityFocused(true);
shouldClearAccessibilityFocus = false;
break;
@@ -718,7 +731,7 @@
}
if (shouldClearAccessibilityFocus) {
- clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
+ clearAccessibilityFocusLocked(a11yFocusedWindowId);
}
}
@@ -1022,6 +1035,7 @@
}
if (proxyed && !observer.mIsProxy) {
observer.mIsProxy = true;
+ mHasProxy = true;
}
if (observer.isTrackingWindowsLocked()) {
return;
@@ -1044,6 +1058,7 @@
observer.stopTrackingWindowsLocked();
mDisplayWindowsObservers.remove(displayId);
}
+ resetHasProxyIfNeededLocked();
}
}
@@ -1053,11 +1068,26 @@
*/
public void stopTrackingDisplayProxy(int displayId) {
synchronized (mLock) {
- DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+ final DisplayWindowsObserver proxyObserver = mDisplayWindowsObservers.get(displayId);
+ if (proxyObserver != null) {
+ proxyObserver.mIsProxy = false;
+ }
+ resetHasProxyIfNeededLocked();
+ }
+ }
+
+ private void resetHasProxyIfNeededLocked() {
+ boolean hasProxy = false;
+ final int count = mDisplayWindowsObservers.size();
+ for (int i = 0; i < count; i++) {
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
- observer.mIsProxy = false;
+ if (observer.mIsProxy) {
+ hasProxy = true;
+ }
}
}
+ mHasProxy = hasProxy;
}
/**
@@ -1490,6 +1520,11 @@
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
synchronized (mLock) {
+ // If window id belongs to a proxy display, then find the display, update the
+ // observer focus and send WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED events.
+ if (mHasProxy && setProxyFocusLocked(windowId)) {
+ return;
+ }
if (mAccessibilityFocusedWindowId != windowId) {
clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
setAccessibilityFocusedWindowLocked(windowId);
@@ -1500,6 +1535,10 @@
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
synchronized (mLock) {
+ // If cleared happened on the proxy display, then clear the tracked focus.
+ if (mHasProxy && clearProxyFocusLocked(windowId, eventAction)) {
+ return;
+ }
if (mAccessibilityFocusNodeId == nodeId) {
mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
}
@@ -1599,9 +1638,10 @@
if (mAccessibilityFocusedDisplayId != Display.INVALID_DISPLAY
&& mAccessibilityFocusedWindowId
!= AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+ // Previously focused window -> send a focused event for losing focus
events.add(AccessibilityEvent.obtainWindowsChangedEvent(
mAccessibilityFocusedDisplayId, mAccessibilityFocusedWindowId,
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
}
mAccessibilityFocusedWindowId = windowId;
@@ -1611,8 +1651,9 @@
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null && observer.setAccessibilityFocusedWindowLocked(windowId)) {
mAccessibilityFocusedDisplayId = observer.mDisplayId;
+ // Newly focused window -> send a focused event for gaining focus
events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId,
- windowId, AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+ windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
}
}
@@ -1662,6 +1703,33 @@
* @return The focused windowId
*/
public int getFocusedWindowId(int focusType) {
+ return getFocusedWindowId(focusType, Display.INVALID_DISPLAY);
+ }
+
+ /**
+ * Returns focused windowId or accessibility focused windowId according to given focusType and
+ * display id.
+ * @param focusType {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}
+ * @param displayId the display id to check. If this display is proxy-ed, the proxy's a11y focus
+ * will be returned.
+ * @return The focused windowId
+ */
+ public int getFocusedWindowId(int focusType, int displayId) {
+ if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY
+ || !mHasProxy) {
+ return getDefaultFocus(focusType);
+ }
+
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+ if (observer != null && observer.mIsProxy) {
+ return getProxyFocus(focusType, observer);
+ } else {
+ return getDefaultFocus(focusType);
+ }
+ }
+
+ private int getDefaultFocus(int focusType) {
if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
return mTopFocusedWindowId;
} else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
@@ -1670,6 +1738,16 @@
return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
+ private int getProxyFocus(int focusType, DisplayWindowsObserver observer) {
+ if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
+ return mTopFocusedWindowId;
+ } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
+ return observer.mProxyDisplayAccessibilityFocusedWindow;
+ } else {
+ return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+ }
+
/**
* Returns {@link AccessibilityWindowInfo} of PIP window.
*
@@ -1988,6 +2066,78 @@
}
/**
+ * Checks if the window belongs to a proxy display and if so clears the focused window id.
+ * @param focusClearedWindowId the cleared window id.
+ * @return true if an observer is proxy-ed and has cleared its focused window id.
+ */
+ private boolean clearProxyFocusLocked(int focusClearedWindowId, int eventAction) {
+ // If we are just moving focus from one view to the other in the same window, do nothing.
+ if (eventAction == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
+ return false;
+ }
+ for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(i);
+ if (observer != null && observer.mWindows != null && observer.mIsProxy) {
+ final int windowCount = observer.mWindows.size();
+ for (int j = 0; j < windowCount; j++) {
+ AccessibilityWindowInfo window = observer.mWindows.get(j);
+ if (window.getId() == focusClearedWindowId) {
+ observer.mProxyDisplayAccessibilityFocusedWindow =
+ AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ // TODO(268754409): Look into sending a WINDOW_FOCUS_CHANGED event since
+ // window no longer has focus (default window logic doesn't), and
+ // whether the node id needs to be cached (default window logic does).
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the window belongs to a proxy display and if so sends
+ * WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED for that window and the previously focused window.
+ * @param focusedWindowId the focused window id.
+ * @return true if an observer is proxy-ed and contains the focused window.
+ */
+ private boolean setProxyFocusLocked(int focusedWindowId) {
+ for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
+ if (observer != null && observer.mIsProxy
+ && observer.setAccessibilityFocusedWindowLocked(focusedWindowId)) {
+ final int previouslyFocusedWindowId =
+ observer.mProxyDisplayAccessibilityFocusedWindow;
+
+ if (previouslyFocusedWindowId == focusedWindowId) {
+ // Don't send a focus event if the window is already focused.
+ return true;
+ }
+
+ // Previously focused window -> Clear focus on UI thread and send a focused event
+ // for losing focus
+ if (previouslyFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+ clearAccessibilityFocusLocked(previouslyFocusedWindowId);
+ mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(
+ observer.mDisplayId, previouslyFocusedWindowId,
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+ }
+ observer.mProxyDisplayAccessibilityFocusedWindow = focusedWindowId;
+ // Newly focused window -> send a focused event for it gaining focus
+ mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(
+ observer.mDisplayId,
+ observer.mProxyDisplayAccessibilityFocusedWindow,
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Dumps all {@link AccessibilityWindowInfo}s here.
*/
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index df913aa..945d43b 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -255,6 +255,24 @@
}
@Override
+ int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+ if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
+ final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType,
+ mDisplayId);
+ // If the caller is a proxy and the found window doesn't belong to a proxy display
+ // (or vice versa), then return null. This doesn't work if there are multiple active
+ // proxys, but in the future this code shouldn't be needed if input focus
+ // properly split. (so we will deal with the issues if we see them).
+ //TODO(254545943): Remove this when there is user and proxy separation of input
+ if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
+ return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+ return focusedWindowId;
+ }
+ return windowId;
+ }
+
+ @Override
public void binderDied() {
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 2530338..9d91d10 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -78,8 +78,7 @@
AccessibilitySecurityPolicy securityPolicy,
AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
AccessibilityTrace trace,
- WindowManagerInternal windowManagerInternal,
- AccessibilityWindowManager awm) throws RemoteException {
+ WindowManagerInternal windowManagerInternal) 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.
@@ -93,7 +92,7 @@
new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
id, mainHandler, mLock, securityPolicy, systemSupport, trace,
windowManagerInternal,
- awm, displayId);
+ mA11yWindowManager, displayId);
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 9c84c04..f85ef43f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -44,6 +44,8 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.util.MathUtils;
@@ -279,7 +281,8 @@
return mTempPointerProperties;
}
- private void transitionTo(State state) {
+ @VisibleForTesting
+ void transitionTo(State state) {
if (DEBUG_STATE_TRANSITIONS) {
Slog.i(mLogTag,
(State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
@@ -287,6 +290,9 @@
.replace(getClass().getName(), ""));
}
mPreviousState = mCurrentState;
+ if (state == mPanningScalingState) {
+ mPanningScalingState.prepareForState();
+ }
mCurrentState = state;
}
@@ -317,18 +323,34 @@
final class PanningScalingState extends SimpleOnGestureListener
implements OnScaleGestureListener, State {
+ private final Context mContext;
private final ScaleGestureDetector mScaleGestureDetector;
private final GestureDetector mScrollGestureDetector;
final float mScalingThreshold;
float mInitialScaleFactor = -1;
- boolean mScaling;
+ @VisibleForTesting boolean mScaling;
+
+ /**
+ * Whether it needs to detect the target scale passes
+ * {@link FullScreenMagnificationController#getPersistedScale} during panning scale.
+ */
+ @VisibleForTesting boolean mDetectingPassPersistedScale;
+
+ // The threshold for relative difference from given scale to persisted scale. If the
+ // difference >= threshold, we can start detecting if the scale passes the persisted
+ // scale during panning.
+ @VisibleForTesting static final float CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD = 0.2f;
+ // The threshold for relative difference from given scale to persisted scale. If the
+ // difference < threshold, we can decide that the scale passes the persisted scale.
+ @VisibleForTesting static final float PASSING_PERSISTED_SCALE_THRESHOLD = 0.01f;
PanningScalingState(Context context) {
final TypedValue scaleValue = new TypedValue();
context.getResources().getValue(
R.dimen.config_screen_magnification_scaling_threshold,
scaleValue, false);
+ mContext = context;
mScalingThreshold = scaleValue.getFloat();
mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
mScaleGestureDetector.setQuickScaleEnabled(false);
@@ -351,12 +373,59 @@
}
}
+
+ void prepareForState() {
+ checkShouldDetectPassPersistedScale();
+ }
+
+ private void checkShouldDetectPassPersistedScale() {
+ if (mDetectingPassPersistedScale) {
+ return;
+ }
+
+ final float currentScale =
+ mFullScreenMagnificationController.getScale(mDisplayId);
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(mDisplayId);
+
+ mDetectingPassPersistedScale =
+ (abs(currentScale - persistedScale) / persistedScale)
+ >= CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ }
+
public void persistScaleAndTransitionTo(State state) {
mFullScreenMagnificationController.persistScale(mDisplayId);
clear();
transitionTo(state);
}
+ @VisibleForTesting
+ void setScaleAndClearIfNeeded(float scale, float pivotX, float pivotY) {
+ if (mDetectingPassPersistedScale) {
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(mDisplayId);
+ // If the scale passes the persisted scale during panning, perform a vibration
+ // feedback to user. Also, call {@link clear} to create a buffer zone so that
+ // user needs to panning more than {@link mScalingThreshold} to change scale again.
+ if (abs(scale - persistedScale) / persistedScale
+ < PASSING_PERSISTED_SCALE_THRESHOLD) {
+ scale = persistedScale;
+ final Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+ if (vibrator != null) {
+ vibrator.vibrate(
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
+ }
+ clear();
+ }
+ }
+
+ if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
+ mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ checkShouldDetectPassPersistedScale();
+ }
+
@Override
public boolean onScroll(MotionEvent first, MotionEvent second,
float distanceX, float distanceY) {
@@ -402,11 +471,7 @@
scale = targetScale;
}
- final float pivotX = detector.getFocusX();
- final float pivotY = detector.getFocusY();
- if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
- mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ setScaleAndClearIfNeeded(scale, detector.getFocusX(), detector.getFocusY());
return /* handled: */ true;
}
@@ -424,6 +489,7 @@
public void clear() {
mInitialScaleFactor = -1;
mScaling = false;
+ mDetectingPassPersistedScale = false;
}
@Override
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b4dcf43..f650560 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -51,6 +51,10 @@
import android.content.pm.ActivityInfo;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualKeyboardConfig;
@@ -82,6 +86,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.server.LocalServices;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
@@ -109,21 +114,29 @@
private final Context mContext;
private final AssociationInfo mAssociationInfo;
+ private final VirtualDeviceManagerService mService;
private final PendingTrampolineCallback mPendingTrampolineCallback;
private final int mOwnerUid;
private final int mDeviceId;
+ // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
+ // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
+ // 1. After display is created the window manager calls into VDM during construction
+ // of display specific context to fetch device id corresponding to the display.
+ // mVirtualDeviceLock will be held while this is done.
+ // 2. InputController interactions result in calls to DisplayManager (to set IME,
+ // possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which
+ // creates lock inversion.
private final InputController mInputController;
private final SensorController mSensorController;
private final CameraAccessController mCameraAccessController;
private VirtualAudioController mVirtualAudioController;
- @VisibleForTesting
- final ArraySet<Integer> mVirtualDisplayIds = new ArraySet<>();
- private final OnDeviceCloseListener mOnDeviceCloseListener;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
- private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
+ @GuardedBy("mVirtualDeviceLock")
+ private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
private final IVirtualDeviceActivityListener mActivityListener;
private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
+ private final DisplayManagerGlobal mDisplayManager;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
@@ -174,21 +187,14 @@
};
}
- /**
- * A mapping from the virtual display ID to its corresponding
- * {@link GenericWindowPolicyController}.
- */
- private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
- new SparseArray<>();
-
VirtualDeviceImpl(
Context context,
AssociationInfo associationInfo,
+ VirtualDeviceManagerService service,
IBinder token,
int ownerUid,
int deviceId,
CameraAccessController cameraAccessController,
- OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
IVirtualDeviceSoundEffectListener soundEffectListener,
@@ -197,40 +203,43 @@
this(
context,
associationInfo,
+ service,
token,
ownerUid,
deviceId,
/* inputController= */ null,
/* sensorController= */ null,
cameraAccessController,
- onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
soundEffectListener,
runningAppsChangedCallback,
- params);
+ params,
+ DisplayManagerGlobal.getInstance());
}
@VisibleForTesting
VirtualDeviceImpl(
Context context,
AssociationInfo associationInfo,
+ VirtualDeviceManagerService service,
IBinder token,
int ownerUid,
int deviceId,
InputController inputController,
SensorController sensorController,
CameraAccessController cameraAccessController,
- OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
- VirtualDeviceParams params) {
+ VirtualDeviceParams params,
+ DisplayManagerGlobal displayManager) {
super(PermissionEnforcer.fromContext(context));
UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
+ mService = service;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
mSoundEffectListener = soundEffectListener;
@@ -239,6 +248,7 @@
mDeviceId = deviceId;
mAppToken = token;
mParams = params;
+ mDisplayManager = displayManager;
if (inputController == null) {
mInputController = new InputController(
mVirtualDeviceLock,
@@ -259,7 +269,6 @@
}
mCameraAccessController = cameraAccessController;
mCameraAccessController.startObservingIfNeeded();
- mOnDeviceCloseListener = onDeviceCloseListener;
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -331,9 +340,11 @@
@Override // Binder call
public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
ResultReceiver resultReceiver) {
- if (!mVirtualDisplayIds.contains(displayId)) {
- throw new SecurityException("Display ID " + displayId
- + " not found for this virtual device");
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ throw new SecurityException("Display ID " + displayId
+ + " not found for this virtual device");
+ }
}
if (pendingIntent.isActivity()) {
try {
@@ -383,24 +394,34 @@
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
super.close_enforcePermission();
+ // Remove about-to-be-closed virtual device from the service before butchering it.
+ mService.removeVirtualDevice(mDeviceId);
+
+ VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
synchronized (mVirtualDeviceLock) {
- if (!mPerDisplayWakelocks.isEmpty()) {
- mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
- Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
- + " was not properly released");
- wakeLock.release();
- });
- mPerDisplayWakelocks.clear();
- }
if (mVirtualAudioController != null) {
mVirtualAudioController.stopListening();
mVirtualAudioController = null;
}
mLocaleList = null;
+ virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
+ }
+ mVirtualDisplays.clear();
mVirtualSensorList = null;
mVirtualSensors.clear();
}
- mOnDeviceCloseListener.onClose(mDeviceId);
+ // Destroy the display outside locked section.
+ for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
+ mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
+ // The releaseVirtualDisplay call above won't trigger
+ // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
+ // virtual device from the service - we release the other display-tied resources here
+ // with the guarantee it will be done exactly once.
+ releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+ }
+
mAppToken.unlinkToDeath(this, 0);
mCameraAccessController.stopObservingIfNeeded();
@@ -429,11 +450,6 @@
return mVirtualAudioController;
}
- @VisibleForTesting
- SparseArray<GenericWindowPolicyController> getWindowPolicyControllersForTesting() {
- return mWindowPolicyControllers;
- }
-
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAudioSessionStarting(int displayId,
@@ -441,7 +457,7 @@
@Nullable IAudioConfigChangedCallback configChangedCallback) {
super.onAudioSessionStarting_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplays.contains(displayId)) {
throw new SecurityException(
"Cannot start audio session for a display not associated with this virtual "
+ "device");
@@ -449,7 +465,8 @@
if (mVirtualAudioController == null) {
mVirtualAudioController = new VirtualAudioController(mContext);
- GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
+ GenericWindowPolicyController gwpc = mVirtualDisplays.get(
+ displayId).getWindowPolicyController();
mVirtualAudioController.startListening(gwpc, routingCallback,
configChangedCallback);
}
@@ -473,7 +490,7 @@
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
super.createVirtualDpad_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual dpad for a display not associated with "
+ "this virtual device");
@@ -493,7 +510,7 @@
public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
super.createVirtualKeyboard_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual keyboard for a display not associated with "
+ "this virtual device");
@@ -515,7 +532,7 @@
public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
super.createVirtualMouse_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual mouse for a display not associated with this "
+ "virtual device");
@@ -536,7 +553,7 @@
@NonNull IBinder deviceToken) {
super.createVirtualTouchscreen_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual touchscreen for a display not associated with "
+ "this virtual device");
@@ -566,7 +583,7 @@
@NonNull IBinder deviceToken) {
super.createVirtualNavigationTouchpad_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual navigation touchpad for a display not associated "
+ "with this virtual device");
@@ -704,7 +721,8 @@
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
- for (int displayId : mVirtualDisplayIds) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ final int displayId = mVirtualDisplays.keyAt(i);
mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
}
}
@@ -795,8 +813,8 @@
fout.println(" mParams: " + mParams);
fout.println(" mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
- for (int id : mVirtualDisplayIds) {
- fout.println(" " + id);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ fout.println(" " + mVirtualDisplays.keyAt(i));
}
fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
@@ -804,61 +822,75 @@
mSensorController.dump(fout);
}
- GenericWindowPolicyController createWindowPolicyController(
+ private GenericWindowPolicyController createWindowPolicyController(
@NonNull List<String> displayCategories) {
- synchronized (mVirtualDeviceLock) {
- final GenericWindowPolicyController gwpc =
- new GenericWindowPolicyController(FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
- getAllowedUserHandles(),
- mParams.getAllowedCrossTaskNavigations(),
- mParams.getBlockedCrossTaskNavigations(),
- mParams.getAllowedActivities(),
- mParams.getBlockedActivities(),
- mParams.getDefaultActivityPolicy(),
- createListenerAdapter(),
- this::onEnteringPipBlocked,
- this::onActivityBlocked,
- this::onSecureWindowShown,
- this::shouldInterceptIntent,
- displayCategories,
- mParams.getDefaultRecentsPolicy());
- gwpc.registerRunningAppsChangedListener(/* listener= */ this);
- return gwpc;
- }
+ final GenericWindowPolicyController gwpc =
+ new GenericWindowPolicyController(FLAG_SECURE,
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ getAllowedUserHandles(),
+ mParams.getAllowedCrossTaskNavigations(),
+ mParams.getBlockedCrossTaskNavigations(),
+ mParams.getAllowedActivities(),
+ mParams.getBlockedActivities(),
+ mParams.getDefaultActivityPolicy(),
+ createListenerAdapter(),
+ this::onEnteringPipBlocked,
+ this::onActivityBlocked,
+ this::onSecureWindowShown,
+ this::shouldInterceptIntent,
+ displayCategories,
+ mParams.getDefaultRecentsPolicy());
+ gwpc.registerRunningAppsChangedListener(/* listener= */ this);
+ return gwpc;
}
- void onVirtualDisplayCreatedLocked(GenericWindowPolicyController gwpc, int displayId) {
+ int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+ @NonNull IVirtualDisplayCallback callback, String packageName) {
+ GenericWindowPolicyController gwpc = createWindowPolicyController(
+ virtualDisplayConfig.getDisplayCategories());
+ DisplayManagerInternal displayManager = LocalServices.getService(
+ DisplayManagerInternal.class);
+ int displayId;
+ displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
+ this, gwpc, packageName);
+ gwpc.setDisplayId(displayId);
+
synchronized (mVirtualDeviceLock) {
- if (displayId == Display.INVALID_DISPLAY) {
- return;
- }
- if (mVirtualDisplayIds.contains(displayId)) {
+ if (mVirtualDisplays.contains(displayId)) {
+ gwpc.unregisterRunningAppsChangedListener(this);
throw new IllegalStateException(
"Virtual device already has a virtual display with ID " + displayId);
}
- mVirtualDisplayIds.add(displayId);
- gwpc.setDisplayId(displayId);
- mWindowPolicyControllers.put(displayId, gwpc);
+ PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
+ mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
mInputController.setPointerAcceleration(1f, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
mInputController.setLocalIme(displayId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return displayId;
+ }
- if (mPerDisplayWakelocks.containsKey(displayId)) {
- Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
- return;
- }
+ private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
TAG + ":" + displayId, displayId);
- mPerDisplayWakelocks.put(displayId, wakeLock);
wakeLock.acquire();
+ return wakeLock;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -872,8 +904,10 @@
}
private void onSecureWindowShown(int displayId, int uid) {
- if (!mVirtualDisplayIds.contains(displayId)) {
- return;
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ return;
+ }
}
// If a virtual display isn't secure, the screen can't be captured. Show a warning toast
@@ -888,55 +922,102 @@
private ArraySet<UserHandle> getAllowedUserHandles() {
ArraySet<UserHandle> result = new ArraySet<>();
- DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
- UserManager userManager = mContext.getSystemService(UserManager.class);
- for (UserHandle profile : userManager.getAllProfiles()) {
- int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
- if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
- || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
- result.add(profile);
- } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
- if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ for (UserHandle profile : userManager.getAllProfiles()) {
+ int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(
+ profile.getIdentifier());
+ if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+ || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
result.add(profile);
+ } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+ if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+ result.add(profile);
+ }
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
return result;
}
- void onVirtualDisplayRemovedLocked(int displayId) {
+
+ void onVirtualDisplayRemoved(int displayId) {
+ /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
+ * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
+ * At this point, the display is already released, but we still need to release the
+ * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
+ * WindowPolicyController.
+ *
+ * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
+ * this callback won't be invoked because the display is removed from
+ * VirtualDeviceManagerService before any resources are released.
+ */
+ VirtualDisplayWrapper virtualDisplayWrapper;
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
- throw new IllegalStateException(
- "Virtual device doesn't have a virtual display with ID " + displayId);
- }
- PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
- if (wakeLock != null) {
- wakeLock.release();
- mPerDisplayWakelocks.remove(displayId);
- }
- GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
- if (gwpc != null) {
- gwpc.unregisterRunningAppsChangedListener(/* listener= */ this);
- }
- mVirtualDisplayIds.remove(displayId);
- mWindowPolicyControllers.remove(displayId);
+ virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId);
}
+
+ if (virtualDisplayWrapper == null) {
+ throw new IllegalStateException(
+ "Virtual device doesn't have a virtual display with ID " + displayId);
+ }
+
+ releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+
+ }
+
+ /**
+ * Release resources tied to virtual display owned by this VirtualDevice instance.
+ *
+ * Note that this method won't release the virtual display itself.
+ *
+ * @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
+ */
+ private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
+ virtualDisplayWrapper.getWakeLock().release();
+ virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
+ this);
}
int getOwnerUid() {
return mOwnerUid;
}
+ ArraySet<Integer> getDisplayIds() {
+ synchronized (mVirtualDeviceLock) {
+ final int size = mVirtualDisplays.size();
+ ArraySet<Integer> arraySet = new ArraySet<>(size);
+ for (int i = 0; i < size; i++) {
+ arraySet.append(mVirtualDisplays.keyAt(i));
+ }
+ return arraySet;
+ }
+ }
+
+ @VisibleForTesting
+ GenericWindowPolicyController getDisplayWindowPolicyControllerForTest(int displayId) {
+ VirtualDisplayWrapper virtualDisplayWrapper;
+ synchronized (mVirtualDeviceLock) {
+ virtualDisplayWrapper = mVirtualDisplays.get(displayId);
+ }
+ return virtualDisplayWrapper != null ? virtualDisplayWrapper.getWindowPolicyController()
+ : null;
+ }
+
/**
* Returns true if an app with the given {@code uid} is currently running on this virtual
* device.
*/
boolean isAppRunningOnVirtualDevice(int uid) {
- final int size = mWindowPolicyControllers.size();
- for (int i = 0; i < size; i++) {
- if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
- return true;
+ synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+ return true;
+ }
}
}
return false;
@@ -957,11 +1038,9 @@
Looper looper) {
synchronized (mVirtualDeviceLock) {
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
- final int size = mWindowPolicyControllers.size();
- for (int i = 0; i < size; i++) {
- if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
- int displayId = mWindowPolicyControllers.keyAt(i);
- Display display = displayManager.getDisplay(displayId);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+ Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
if (display != null && display.isValid()) {
Toast.makeText(mContext.createDisplayContext(display), looper, text,
duration).show();
@@ -972,7 +1051,9 @@
}
boolean isDisplayOwnedByVirtualDevice(int displayId) {
- return mVirtualDisplayIds.contains(displayId);
+ synchronized (mVirtualDeviceLock) {
+ return mVirtualDisplays.contains(displayId);
+ }
}
void onEnteringPipBlocked(int uid) {
@@ -1016,10 +1097,6 @@
}
}
- interface OnDeviceCloseListener {
- void onClose(int deviceId);
- }
-
interface PendingTrampolineCallback {
/**
* Called when the callback should start waiting for the given pending trampoline.
@@ -1073,4 +1150,31 @@
+ ", displayId=" + mDisplayId + "}";
}
}
+
+ /** Data class wrapping resources tied to single virtual display. */
+ private static final class VirtualDisplayWrapper {
+ private final IVirtualDisplayCallback mToken;
+ private final GenericWindowPolicyController mWindowPolicyController;
+ private final PowerManager.WakeLock mWakeLock;
+
+ VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
+ @NonNull GenericWindowPolicyController windowPolicyController,
+ @NonNull PowerManager.WakeLock wakeLock) {
+ mToken = Objects.requireNonNull(token);
+ mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
+ mWakeLock = Objects.requireNonNull(wakeLock);
+ }
+
+ GenericWindowPolicyController getWindowPolicyController() {
+ return mWindowPolicyController;
+ }
+
+ PowerManager.WakeLock getWakeLock() {
+ return mWakeLock;
+ }
+
+ IVirtualDisplayCallback getToken() {
+ return mToken;
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9bb05a6..ed5b858 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -37,7 +37,6 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.content.Context;
import android.content.Intent;
-import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Binder;
@@ -108,26 +107,26 @@
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
- @Nullable
- @Override
- public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
- ActivityInterceptorInfo info) {
- if (info.getCallingPackage() == null) {
- return null;
- }
- PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
- if (pt == null) {
- return null;
- }
- pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
- ActivityOptions options = info.getCheckedOptions();
- if (options == null) {
- options = ActivityOptions.makeBasic();
- }
- return new ActivityInterceptResult(
- info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
- }
- };
+ @Nullable
+ @Override
+ public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+ ActivityInterceptorInfo info) {
+ if (info.getCallingPackage() == null) {
+ return null;
+ }
+ PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
+ if (pt == null) {
+ return null;
+ }
+ pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
+ ActivityOptions options = info.getCheckedOptions();
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ return new ActivityInterceptResult(
+ info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
+ }
+ };
@Override
public void onStart() {
@@ -146,8 +145,8 @@
CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
getContext().getString(
- com.android.internal.R.string.vdm_camera_access_denied,
- deviceName),
+ com.android.internal.R.string.vdm_camera_access_denied,
+ deviceName),
Toast.LENGTH_LONG, Looper.myLooper());
}
}
@@ -193,34 +192,46 @@
}
}
- @VisibleForTesting
void removeVirtualDevice(int deviceId) {
synchronized (mVirtualDeviceManagerLock) {
mAppsOnVirtualDevices.remove(deviceId);
mVirtualDevices.remove(deviceId);
}
+
+ Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+ i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+ i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
new VirtualDeviceImpl.PendingTrampolineCallback() {
- @Override
- public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
- PendingTrampoline existing = mPendingTrampolines.put(
- pendingTrampoline.mPendingIntent.getCreatorPackage(),
- pendingTrampoline);
- if (existing != null) {
- existing.mResultReceiver.send(
- VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
- }
- }
+ @Override
+ public void startWaitingForPendingTrampoline(
+ PendingTrampoline pendingTrampoline) {
+ PendingTrampoline existing = mPendingTrampolines.put(
+ pendingTrampoline.mPendingIntent.getCreatorPackage(),
+ pendingTrampoline);
+ if (existing != null) {
+ existing.mResultReceiver.send(
+ VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
+ }
+ }
- @Override
- public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
- mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
- }
- };
+ @Override
+ public void stopWaitingForPendingTrampoline(
+ PendingTrampoline pendingTrampoline) {
+ mPendingTrampolines.remove(
+ pendingTrampoline.mPendingIntent.getCreatorPackage());
+ }
+ };
@Override // Binder call
public IVirtualDevice createVirtualDevice(
@@ -251,8 +262,9 @@
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
- associationInfo, token, callingUid, deviceId, cameraAccessController,
- this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
+ associationInfo, VirtualDeviceManagerService.this, token, callingUid,
+ deviceId, cameraAccessController,
+ mPendingTrampolineCallback, activityListener,
soundEffectListener, runningAppsChangedCallback, params);
mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
@@ -281,26 +293,9 @@
"uid " + callingUid
+ " is not the owner of the supplied VirtualDevice");
}
- GenericWindowPolicyController gwpc;
- final long token = Binder.clearCallingIdentity();
- try {
- gwpc = virtualDeviceImpl.createWindowPolicyController(
- virtualDisplayConfig.getDisplayCategories());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- DisplayManagerInternal displayManager = getLocalService(
- DisplayManagerInternal.class);
- int displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
- virtualDevice, gwpc, packageName);
-
- final long tokenTwo = Binder.clearCallingIdentity();
- try {
- virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId);
- } finally {
- Binder.restoreCallingIdentity(tokenTwo);
- }
+ int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback,
+ packageName);
mLocalService.onVirtualDisplayCreated(displayId);
return displayId;
}
@@ -412,19 +407,6 @@
return null;
}
- private void onDeviceClosed(int deviceId) {
- removeVirtualDevice(deviceId);
- Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
- i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
- i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- final long identity = Binder.clearCallingIdentity();
- try {
- getContext().sendBroadcastAsUser(i, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -512,9 +494,14 @@
@Override
public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
final VirtualDisplayListener[] listeners;
+ VirtualDeviceImpl virtualDeviceImpl;
synchronized (mVirtualDeviceManagerLock) {
- ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
+ virtualDeviceImpl = mVirtualDevices.get(
+ ((VirtualDeviceImpl) virtualDevice).getDeviceId());
+ }
+ if (virtualDeviceImpl != null) {
+ virtualDeviceImpl.onVirtualDisplayRemoved(displayId);
}
mHandler.post(() -> {
for (VirtualDisplayListener listener : listeners) {
@@ -599,16 +586,11 @@
@Override
public @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId) {
+ VirtualDeviceImpl virtualDevice;
synchronized (mVirtualDeviceManagerLock) {
- int size = mVirtualDevices.size();
- for (int i = 0; i < size; i++) {
- VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
- if (device.getDeviceId() == deviceId) {
- return new ArraySet<>(device.mVirtualDisplayIds);
- }
- }
+ virtualDevice = mVirtualDevices.get(deviceId);
}
- return new ArraySet<>();
+ return virtualDevice == null ? new ArraySet<>() : virtualDevice.getDisplayIds();
}
@Override
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0252492..e0d1c1e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2753,6 +2753,8 @@
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
+ int avgWriteAmount = 0;
+ int targetDirtyRatio = mTargetDirtyRatio;
int latestWrite = mVold.getWriteAmount();
if (latestWrite == -1) {
Slog.w(TAG, "Failed to get storage write record");
@@ -2765,11 +2767,12 @@
// (first boot after OTA), We skip the smart idle maintenance
if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
if (!refreshLifetimeConstraint() || !checkChargeStatus()) {
- return;
+ Slog.i(TAG, "Turn off gc_urgent based on checking lifetime and charge status");
+ targetDirtyRatio = 100;
+ } else {
+ avgWriteAmount = getAverageWriteAmount();
}
- int avgWriteAmount = getAverageWriteAmount();
-
Slog.i(TAG, "Set smart idle maintenance: " + "latest write amount: " +
latestWrite + ", average write amount: " + avgWriteAmount +
", min segment threshold: " + mMinSegmentsThreshold +
@@ -2777,10 +2780,10 @@
", segment reclaim weight: " + mSegmentReclaimWeight +
", period(min): " + sSmartIdleMaintPeriod +
", min gc sleep time(ms): " + mMinGCSleepTime +
- ", target dirty ratio: " + mTargetDirtyRatio);
+ ", target dirty ratio: " + targetDirtyRatio);
mVold.setGCUrgentPace(avgWriteAmount, mMinSegmentsThreshold, mDirtyReclaimRate,
mSegmentReclaimWeight, sSmartIdleMaintPeriod,
- mMinGCSleepTime, mTargetDirtyRatio);
+ mMinGCSleepTime, targetDirtyRatio);
} else {
Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress");
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d5d0edc..70304c5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -522,6 +522,7 @@
final ArrayList<ServiceRecord> mStartingBackground = new ArrayList<>();
final ArrayMap<String, ActiveForegroundApp> mActiveForegroundApps = new ArrayMap<>();
+ final ArrayList<String> mPendingRemoveForegroundApps = new ArrayList<>();
boolean mActiveForegroundAppsChanged;
@@ -1687,13 +1688,14 @@
if (smap != null) {
if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user "
+ smap.mUserId);
+ smap.mPendingRemoveForegroundApps.clear();
for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
if (aa.mEndTime != 0) {
boolean canRemove = foregroundAppShownEnoughLocked(aa, now);
if (canRemove) {
// This was up for longer than the timeout, so just remove immediately.
- smap.mActiveForegroundApps.removeAt(i);
+ smap.mPendingRemoveForegroundApps.add(smap.mActiveForegroundApps.keyAt(i));
smap.mActiveForegroundAppsChanged = true;
continue;
}
@@ -1718,6 +1720,9 @@
}
}
}
+ for(int i = smap.mPendingRemoveForegroundApps.size() - 1; i >= 0; i--) {
+ smap.mActiveForegroundApps.remove(smap.mPendingRemoveForegroundApps.get(i));
+ }
smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
if (nextUpdateTime < Long.MAX_VALUE) {
if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: "
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 6619025..c2326f6 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -1388,7 +1388,7 @@
final int mDefaultBgCurrentDrainPowerComponent;
/**
- * Default value to {@link #mBgCurrentDrainExmptedTypes}.
+ * Default value to {@link #mBgCurrentDrainExemptedTypes}.
**/
final int mDefaultBgCurrentDrainExemptedTypes;
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index c0cb7d9..993595b 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -316,6 +316,10 @@
// then we should care, otherwise we assume
// it's not related to any FGS
UidState uidState = mUids.get(uid);
+ if (uidState == null) {
+ Log.w(TAG, "API event end called before start!");
+ return -1;
+ }
if (uidState.mOpenWithFgsCount.contains(apiType)) {
// are there any calls that started with an FGS?
if (uidState.mOpenWithFgsCount.get(apiType) != 0) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ab2c002..1ce917c 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3254,6 +3254,8 @@
}
mActiveNetwork = network;
+ mUnderlyingLinkProperties = null;
+ mUnderlyingNetworkCapabilities = null;
mRetryCount = 0;
startOrMigrateIkeSession(network);
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 8e34601..fcaa957 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -4,5 +4,7 @@
ogunwale@google.com
santoscordon@google.com
flc@google.com
+wilczynskip@google.com
+brup@google.com
per-file ColorDisplayService.java=christyfranks@google.com
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index da65f27..2efb0be 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -24,13 +24,14 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.util.Log;
import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.view.IImeTracker;
import java.io.PrintWriter;
import java.time.Instant;
@@ -53,7 +54,7 @@
@SuppressWarnings("GuardedBy")
public final class ImeTrackerService extends IImeTracker.Stub {
- static final String TAG = "ImeTrackerService";
+ private static final String TAG = ImeTracker.TAG;
/** The threshold in milliseconds after which a history entry is considered timed out. */
private static final long TIMEOUT_MS = 10_000;
@@ -71,67 +72,71 @@
@NonNull
@Override
- public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
- final IBinder binder = new Binder();
- final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
- ImeTracker.STATUS_RUN, origin, reason);
+ public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ final var binder = new Binder();
+ final var token = new ImeTracker.Token(binder, tag);
+ final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
+ origin, reason);
mHistory.addEntry(binder, entry);
// Register a delayed task to handle the case where the new entry times out.
mHandler.postDelayed(() -> {
synchronized (ImeTrackerService.this) {
- mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
}
}, TIMEOUT_MS);
- return binder;
+ return token;
}
@NonNull
@Override
- public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
- final IBinder binder = new Binder();
- final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
- ImeTracker.STATUS_RUN, origin, reason);
+ public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ final var binder = new Binder();
+ final var token = new ImeTracker.Token(binder, tag);
+ final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
+ origin, reason);
mHistory.addEntry(binder, entry);
// Register a delayed task to handle the case where the new entry times out.
mHandler.postDelayed(() -> {
synchronized (ImeTrackerService.this) {
- mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
}
}, TIMEOUT_MS);
- return binder;
+ return token;
}
@Override
- public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
- final History.Entry entry = mHistory.getEntry(statsToken);
+ public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+ final var entry = mHistory.getEntry(binder);
if (entry == null) return;
entry.mPhase = phase;
}
@Override
- public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
+ @ImeTracker.Phase int phase) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
}
@Override
- public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
+ @ImeTracker.Phase int phase) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
}
@Override
- public synchronized void onShown(@NonNull IBinder statsToken) {
+ public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
}
@Override
- public synchronized void onHidden(@NonNull IBinder statsToken) {
+ public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
}
@@ -141,9 +146,9 @@
* @param statsToken the token corresponding to the current IME request.
* @param requestWindowName the name of the window that created the IME request.
*/
- public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+ public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
@NonNull String requestWindowName) {
- final History.Entry entry = mHistory.getEntry(statsToken);
+ final var entry = mHistory.getEntry(statsToken.getBinder());
if (entry == null) return;
entry.mRequestWindowName = requestWindowName;
@@ -181,17 +186,17 @@
/** Latest entry sequence number. */
private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
- /** Adds a live entry. */
+ /** Adds a live entry corresponding to the given IME tracking token's binder. */
@GuardedBy("ImeTrackerService.this")
- private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
- mLiveEntries.put(statsToken, entry);
+ private void addEntry(@NonNull IBinder binder, @NonNull Entry entry) {
+ mLiveEntries.put(binder, entry);
}
- /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+ /** Gets the entry corresponding to the given IME tracking token's binder, if it exists. */
@Nullable
@GuardedBy("ImeTrackerService.this")
- private Entry getEntry(@NonNull IBinder statsToken) {
- return mLiveEntries.get(statsToken);
+ private Entry getEntry(@NonNull IBinder binder) {
+ return mLiveEntries.get(binder);
}
/**
@@ -204,10 +209,21 @@
* (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
*/
@GuardedBy("ImeTrackerService.this")
- private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
- @ImeTracker.Phase int phase) {
- final Entry entry = mLiveEntries.remove(statsToken);
- if (entry == null) return;
+ private void setFinished(@NonNull ImeTracker.Token statsToken,
+ @ImeTracker.Status int status, @ImeTracker.Phase int phase) {
+ final var entry = mLiveEntries.remove(statsToken.getBinder());
+ if (entry == null) {
+ // This will be unconditionally called through the postDelayed above to handle
+ // potential timeouts, and is thus intentionally dropped to avoid having to manually
+ // save and remove the registered callback. Only timeout calls are expected.
+ if (status != ImeTracker.STATUS_TIMEOUT) {
+ Log.i(TAG, statsToken.getTag()
+ + ": setFinished on previously finished token at "
+ + ImeTracker.Debug.phaseToString(phase) + " with "
+ + ImeTracker.Debug.statusToString(status));
+ }
+ return;
+ }
entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
entry.mStatus = status;
@@ -216,6 +232,13 @@
entry.mPhase = phase;
}
+ if (status == ImeTracker.STATUS_TIMEOUT) {
+ // All events other than timeouts are already logged in the client-side ImeTracker.
+ Log.i(TAG, statsToken.getTag() + ": setFinished at "
+ + ImeTracker.Debug.phaseToString(entry.mPhase) + " with "
+ + ImeTracker.Debug.statusToString(status));
+ }
+
// Remove excess entries overflowing capacity (plus one for the new entry).
while (mEntries.size() >= CAPACITY) {
mEntries.remove();
@@ -232,21 +255,22 @@
/** Dumps the contents of the circular buffer. */
@GuardedBy("ImeTrackerService.this")
private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
+ final var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
pw.print(prefix);
- pw.println("ImeTrackerService#History.mLiveEntries:");
+ pw.println("ImeTrackerService#History.mLiveEntries: "
+ + mLiveEntries.size() + " elements");
- for (final Entry entry: mLiveEntries.values()) {
+ for (final var entry: mLiveEntries.values()) {
dumpEntry(entry, pw, prefix, formatter);
}
pw.print(prefix);
- pw.println("ImeTrackerService#History.mEntries:");
+ pw.println("ImeTrackerService#History.mEntries: "
+ + mEntries.size() + " elements");
- for (final Entry entry: mEntries) {
+ for (final var entry: mEntries) {
dumpEntry(entry, pw, prefix, formatter);
}
}
@@ -255,34 +279,22 @@
private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
@NonNull String prefix, @NonNull DateTimeFormatter formatter) {
pw.print(prefix);
- pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+ pw.print(" #" + entry.mSequenceNumber);
+ pw.print(" " + ImeTracker.Debug.typeToString(entry.mType));
+ pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus));
+ pw.print(" - " + entry.mTag);
+ pw.println(" (" + entry.mDuration + "ms):");
pw.print(prefix);
- pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+ pw.print(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+ pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin));
pw.print(prefix);
- pw.println(" duration=" + entry.mDuration + "ms");
+ pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+ pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase));
pw.print(prefix);
- pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
-
- pw.print(prefix);
- pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
-
- pw.print(prefix);
- pw.print(" origin="
- + ImeTracker.Debug.originToString(entry.mOrigin));
-
- pw.print(prefix);
- pw.print(" reason="
- + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
-
- pw.print(prefix);
- pw.print(" phase="
- + ImeTracker.Debug.phaseToString(entry.mPhase));
-
- pw.print(prefix);
- pw.print(" requestWindowName=" + entry.mRequestWindowName);
+ pw.println(" requestWindowName=" + entry.mRequestWindowName);
}
/** A history entry. */
@@ -291,6 +303,10 @@
/** The entry's sequence number in the history. */
private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+ /** Logging tag, of the shape "component:random_hexadecimal". */
+ @NonNull
+ private final String mTag;
+
/** Uid of the client that requested the IME. */
private final int mUid;
@@ -323,13 +339,15 @@
/**
* Name of the window that created the IME request.
*
- * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+ * Note: This is later set through {@link #onImmsUpdate}.
*/
@NonNull
private String mRequestWindowName = "not set";
- private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
- @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ private Entry(@NonNull String tag, int uid, @ImeTracker.Type int type,
+ @ImeTracker.Status int status, @ImeTracker.Origin int origin,
+ @SoftInputShowHideReason int reason) {
+ mTag = tag;
mUid = uid;
mType = type;
mStatus = status;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8ef4e4a..f142293 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -145,6 +145,7 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethod;
@@ -168,7 +169,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.view.IImeTracker;
import com.android.internal.view.IInputMethodManager;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
@@ -532,6 +532,7 @@
/**
* The client that is currently bound to an input method.
*/
+ @Nullable
private ClientState mCurClient;
/**
@@ -557,11 +558,26 @@
int mCurFocusedWindowSoftInputMode;
/**
- * The client by which {@link #mCurFocusedWindow} was reported.
+ * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
+ * IME-focusable window gained focus (without necessarily starting an input connection),
+ * while {@link #mCurClient} only gets updated when we actually start an input connection.
+ *
+ * @see #mCurFocusedWindow
*/
+ @Nullable
ClientState mCurFocusedWindowClient;
/**
+ * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
+ * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
+ * from {@link #mCurClient}.
+ *
+ * @see #mCurFocusedWindow
+ */
+ @Nullable
+ EditorInfo mCurFocusedWindowEditorInfo;
+
+ /**
* The {@link IRemoteInputConnection} last provided by the current client.
*/
IRemoteInputConnection mCurInputConnection;
@@ -580,6 +596,7 @@
/**
* The {@link EditorInfo} last provided by the current client.
*/
+ @Nullable
EditorInfo mCurEditorInfo;
/**
@@ -2265,6 +2282,7 @@
}
if (mCurFocusedWindowClient == cs) {
mCurFocusedWindowClient = null;
+ mCurFocusedWindowEditorInfo = null;
}
}
}
@@ -3453,10 +3471,7 @@
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
// Create statsToken is none exists.
if (statsToken == null) {
- // TODO(b/261565259): to avoid using null, add package name in ClientState
- final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
- final int uid = mCurClient != null ? mCurClient.mUid : -1;
- statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
+ statsToken = createStatsTokenForFocusedClient(true /* show */,
ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
}
@@ -3530,17 +3545,7 @@
int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
// Create statsToken is none exists.
if (statsToken == null) {
- // TODO(b/261565259): to avoid using null, add package name in ClientState
- final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
- final int uid;
- if (mCurClient != null) {
- uid = mCurClient.mUid;
- } else if (mCurFocusedWindowClient != null) {
- uid = mCurFocusedWindowClient.mUid;
- } else {
- uid = -1;
- }
- statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
+ statsToken = createStatsTokenForFocusedClient(false /* show */,
ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
}
@@ -3775,6 +3780,7 @@
mCurFocusedWindow = windowToken;
mCurFocusedWindowSoftInputMode = softInputMode;
mCurFocusedWindowClient = cs;
+ mCurFocusedWindowEditorInfo = editorInfo;
mCurPerceptible = true;
// We want to start input before showing the IME, but after closing
@@ -4703,13 +4709,13 @@
mWindowManagerInternal.onToggleImeRequested(
show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
+ mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
info.imeSurfaceParentName));
if (statsToken != null) {
- mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
+ mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
}
}
@@ -5751,9 +5757,11 @@
// We cannot simply distinguish a bad IME that reports an arbitrary package name from
// an unfortunate IME whose internal state is already obsolete due to the asynchronous
// nature of our system. Let's compare it with our internal record.
- if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) {
+ final var curPackageName = mCurEditorInfo != null
+ ? mCurEditorInfo.packageName : null;
+ if (!TextUtils.equals(curPackageName, packageName)) {
Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
- + mCurEditorInfo.packageName + " packageName=" + packageName);
+ + curPackageName + " packageName=" + packageName);
return null;
}
// This user ID can never bee spoofed.
@@ -6514,6 +6522,30 @@
return mImeTrackerService;
}
+ /**
+ * Creates an IME request tracking token for the current focused client.
+ *
+ * @param show whether this is a show or a hide request.
+ * @param origin the origin of the IME request.
+ * @param reason the reason why the IME request was created.
+ */
+ @NonNull
+ private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ final int uid = mCurFocusedWindowClient != null
+ ? mCurFocusedWindowClient.mUid
+ : -1;
+ final var packageName = mCurFocusedWindowEditorInfo != null
+ ? mCurFocusedWindowEditorInfo.packageName
+ : "uid(" + uid + ")";
+
+ if (show) {
+ return ImeTracker.forLogging().onRequestShow(packageName, uid, origin, reason);
+ } else {
+ return ImeTracker.forLogging().onRequestHide(packageName, uid, origin, reason);
+ }
+ }
+
private static final class InputMethodPrivilegedOperationsImpl
extends IInputMethodPrivilegedOperations.Stub {
private final InputMethodManagerService mImms;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 8f65775..94f12dd 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -370,11 +370,6 @@
mLastRestartTimestampMap.put(contextHubId,
new AtomicLong(SystemClock.elapsedRealtimeNanos()));
- IContextHubClient client = mClientManager.registerClient(
- contextHubInfo, createDefaultClientCallback(contextHubId),
- /* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
- defaultClientMap.put(contextHubId, client);
-
try {
mContextHubWrapper.registerCallback(contextHubId,
new ContextHubServiceCallback(contextHubId));
@@ -383,6 +378,11 @@
+ contextHubId + ")", e);
}
+ IContextHubClient client = mClientManager.registerClient(
+ contextHubInfo, createDefaultClientCallback(contextHubId),
+ /* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
+ defaultClientMap.put(contextHubId, client);
+
// Do a query to initialize the service cache list of nanoapps
// TODO(b/194289715): Remove this when old API is deprecated
queryNanoAppsInternal(contextHubId);
@@ -1207,7 +1207,7 @@
pw.println("");
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
- mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> pw.println(info));
+ mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println);
pw.println("");
pw.println("=================== PRELOADED NANOAPPS ====================");
@@ -1255,16 +1255,17 @@
proto.flush();
}
- /**
- * Dumps preloaded nanoapps to the console
- */
+ /** Dumps preloaded nanoapps to the console */
private void dumpPreloadedNanoapps(PrintWriter pw) {
if (mContextHubWrapper == null) {
return;
}
long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
- for (long preloadedNanoappId: preloadedNanoappIds) {
+ if (preloadedNanoappIds == null) {
+ return;
+ }
+ for (long preloadedNanoappId : preloadedNanoappIds) {
pw.print("ID: 0x");
pw.println(Long.toHexString(preloadedNanoappId));
}
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index 7feb85f..daa0211 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1 +1,3 @@
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index ed3248ec..de631bb 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -181,24 +181,6 @@
}
/**
- * Clears the cached NTP time. For use during tests to simulate when no NTP time is available.
- *
- * <p>This operation takes place in the calling thread rather than the service's handler thread.
- */
- @RequiresPermission(android.Manifest.permission.SET_TIME)
- void clearTimeForTests() {
- mContext.enforceCallingPermission(
- android.Manifest.permission.SET_TIME, "clear latest network time");
-
- final long token = Binder.clearCallingIdentity();
- try {
- mNtpTrustedTime.clearCachedTimeResult();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
* Forces the service to refresh the NTP time.
*
* <p>This operation takes place in the calling thread rather than the service's handler thread.
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
index afc0bdd..cfc95df 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
@@ -37,11 +37,6 @@
private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service";
/**
- * A shell command that clears the time signal received from the network.
- */
- private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time";
-
- /**
* A shell command that forces the time signal to be refreshed from the network.
*/
private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
@@ -73,8 +68,6 @@
}
switch (cmd) {
- case SHELL_COMMAND_CLEAR_TIME:
- return runClearTime();
case SHELL_COMMAND_FORCE_REFRESH:
return runForceRefresh();
case SHELL_COMMAND_SET_SERVER_CONFIG:
@@ -87,11 +80,6 @@
}
}
- private int runClearTime() {
- mNetworkTimeUpdateService.clearTimeForTests();
- return 0;
- }
-
private int runForceRefresh() {
boolean success = mNetworkTimeUpdateService.forceRefreshForTests();
getOutPrintWriter().println(success);
@@ -147,8 +135,6 @@
pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
pw.printf(" help\n");
pw.printf(" Print this help text.\n");
- pw.printf(" %s\n", SHELL_COMMAND_CLEAR_TIME);
- pw.printf(" Clears the latest time.\n");
pw.printf(" %s\n", SHELL_COMMAND_FORCE_REFRESH);
pw.printf(" Refreshes the latest time. Prints whether it was successful.\n");
pw.printf(" %s\n", SHELL_COMMAND_SET_SERVER_CONFIG);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 0da967a..22f096b 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -37,6 +37,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.NtpTrustedTime;
@@ -53,6 +54,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.net.InetSocketAddress;
import java.time.DateTimeException;
import java.util.Objects;
@@ -377,7 +379,7 @@
*
* <p>This operation takes place in the calling thread.
*/
- void clearNetworkTime() {
+ void clearLatestNetworkTime() {
enforceSuggestNetworkTimePermission();
final long token = Binder.clearCallingIdentity();
@@ -390,12 +392,29 @@
@Override
public UnixEpochTime latestNetworkTime() {
- NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion();
- if (suggestion != null) {
- return suggestion.getUnixEpochTime();
+ NetworkTimeSuggestion latestNetworkTime;
+ // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+ // NtpTrustedTime result in a suggestion being made to the time detector.
+ // mNtpTrustedTime can be removed once this happens.
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ // The new implementation.
+ latestNetworkTime = mTimeDetectorStrategy.getLatestNetworkSuggestion();
} else {
+ // The old implementation.
+ NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+ if (ntpResult != null) {
+ latestNetworkTime = new NetworkTimeSuggestion(
+ new UnixEpochTime(
+ ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis()),
+ ntpResult.getUncertaintyMillis());
+ } else {
+ latestNetworkTime = null;
+ }
+ }
+ if (latestNetworkTime == null) {
throw new ParcelableException(new DateTimeException("Missing network time fix"));
}
+ return latestNetworkTime.getUnixEpochTime();
}
/**
@@ -403,23 +422,7 @@
*/
@Nullable
NetworkTimeSuggestion getLatestNetworkSuggestion() {
- // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
- // be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
- // detector. mNtpTrustedTime can be removed once this happens.
- if (TimeDetectorNetworkTimeHelper.isInUse()) {
- // The new implementation.
- return mTimeDetectorStrategy.getLatestNetworkSuggestion();
- } else {
- // The old implementation.
- NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
- if (ntpResult != null) {
- UnixEpochTime unixEpochTime = new UnixEpochTime(
- ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
- return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
- } else {
- return null;
- }
- }
+ return mTimeDetectorStrategy.getLatestNetworkSuggestion();
}
/**
@@ -440,6 +443,57 @@
mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal));
}
+ /**
+ * Sets the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+ *
+ * <p>This operation takes place in the calling thread.
+ */
+ void setNetworkTimeForSystemClockForTests(
+ @NonNull UnixEpochTime unixEpochTime, int uncertaintyMillis) {
+ enforceSuggestNetworkTimePermission();
+
+ // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+ // NtpTrustedTime result in a suggestion being made to the time detector.
+ // mNtpTrustedTime can be removed once this happens.
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ NetworkTimeSuggestion suggestion =
+ new NetworkTimeSuggestion(unixEpochTime, uncertaintyMillis);
+ suggestion.addDebugInfo("Injected for tests");
+ mTimeDetectorStrategy.suggestNetworkTime(suggestion);
+ } else {
+ NtpTrustedTime.TimeResult timeResult = new NtpTrustedTime.TimeResult(
+ unixEpochTime.getUnixEpochTimeMillis(),
+ unixEpochTime.getElapsedRealtimeMillis(),
+ uncertaintyMillis,
+ InetSocketAddress.createUnresolved("time.set.for.tests", 123));
+ mNtpTrustedTime.setCachedTimeResult(timeResult);
+ }
+ }
+
+ /**
+ * Clears the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+ *
+ * <p>This operation takes place in the calling thread.
+ */
+ void clearNetworkTimeForSystemClockForTests() {
+ enforceSuggestNetworkTimePermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+ // NtpTrustedTime result in a suggestion being made to the time detector.
+ // mNtpTrustedTime can be removed once this happens.
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ // Clear the latest network suggestion. Done in all c
+ mTimeDetectorStrategy.clearLatestNetworkSuggestion();
+ } else {
+ mNtpTrustedTime.clearCachedTimeResult();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
index cce5709..fe0127f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
@@ -16,12 +16,14 @@
package com.android.server.timedetector;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_NETWORK_TIME;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_NETWORK_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME;
@@ -73,9 +75,9 @@
case SHELL_COMMAND_SUGGEST_NETWORK_TIME:
return runSuggestNetworkTime();
case SHELL_COMMAND_GET_NETWORK_TIME:
- return runGetNetworkTime();
+ return runGetLatestNetworkTime();
case SHELL_COMMAND_CLEAR_NETWORK_TIME:
- return runClearNetworkTime();
+ return runClearLatestNetworkTime();
case SHELL_COMMAND_SUGGEST_GNSS_TIME:
return runSuggestGnssTime();
case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
@@ -86,6 +88,10 @@
return runSetTimeState();
case SHELL_COMMAND_CONFIRM_TIME:
return runConfirmTime();
+ case SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME:
+ return runClearSystemClockNetworkTime();
+ case SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME:
+ return runSetSystemClockNetworkTime();
default: {
return handleDefaultCommands(cmd);
}
@@ -128,15 +134,15 @@
mInterface::suggestNetworkTime);
}
- private int runGetNetworkTime() {
+ private int runGetLatestNetworkTime() {
NetworkTimeSuggestion networkTimeSuggestion = mInterface.getLatestNetworkSuggestion();
final PrintWriter pw = getOutPrintWriter();
pw.println(networkTimeSuggestion);
return 0;
}
- private int runClearNetworkTime() {
- mInterface.clearNetworkTime();
+ private int runClearLatestNetworkTime() {
+ mInterface.clearLatestNetworkTime();
return 0;
}
@@ -187,6 +193,20 @@
return 0;
}
+ private int runClearSystemClockNetworkTime() {
+ mInterface.clearNetworkTimeForSystemClockForTests();
+ return 0;
+ }
+
+ private int runSetSystemClockNetworkTime() {
+ NetworkTimeSuggestion networkTimeSuggestion =
+ NetworkTimeSuggestion.parseCommandLineArg(this);
+ mInterface.setNetworkTimeForSystemClockForTests(
+ networkTimeSuggestion.getUnixEpochTime(),
+ networkTimeSuggestion.getUncertaintyMillis());
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -218,6 +238,16 @@
pw.printf(" Prints the network time information held by the detector.\n");
pw.printf(" %s\n", SHELL_COMMAND_CLEAR_NETWORK_TIME);
pw.printf(" Clears the network time information held by the detector.\n");
+ // TODO(b/222295093) Remove these "system_clock" commands when
+ // SystemClock.currentNetworkTimeClock() is guaranteed to use the latest network
+ // suggestion. Then, commands above can be used instead.
+ pw.printf(" %s <network suggestion opts>\n",
+ SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME);
+ pw.printf(" Sets the network time information used for"
+ + " SystemClock.currentNetworkTimeClock().\n");
+ pw.printf(" %s\n", SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME);
+ pw.printf(" Clears the network time information used for"
+ + " SystemClock.currentNetworkTimeClock().\n");
pw.println();
ManualTimeSuggestion.printCommandLineOpts(pw);
pw.println();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f6cdec4..18be2a3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -202,6 +202,7 @@
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -228,7 +229,6 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationRunner;
-import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
@@ -1853,11 +1853,11 @@
@Override
public BackNavigationInfo startBackNavigation(
- IWindowFocusObserver observer, BackAnimationAdapter adapter) {
+ RemoteCallback navigationObserver, BackAnimationAdapter adapter) {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startBackNavigation()");
- return mBackNavigationController.startBackNavigation(observer, adapter);
+ return mBackNavigationController.startBackNavigation(navigationObserver, adapter);
}
/**
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 75c85d7..4e94f96 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -253,7 +253,7 @@
ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
- if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) {
+ if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringTransition()) {
tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
if (mDisplayContent.mAtmService.mBackNavigationController
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index bc5f67b..a229fc5 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -41,7 +41,6 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowInsets;
@@ -66,11 +65,11 @@
class BackNavigationController {
private static final String TAG = "BackNavigationController";
private WindowManagerService mWindowManagerService;
- private IWindowFocusObserver mFocusObserver;
private boolean mBackAnimationInProgress;
private @BackNavigationInfo.BackTargetType int mLastBackType;
private boolean mShowWallpaper;
private Runnable mPendingAnimation;
+ private final NavigationMonitor mNavigationMonitor = new NavigationMonitor();
private AnimationHandler mAnimationHandler;
private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
@@ -86,6 +85,11 @@
return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
}
+ // Notify focus window changed
+ void onFocusChanged(WindowState newFocus) {
+ mNavigationMonitor.onFocusWindowChanged(newFocus);
+ }
+
/**
* Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
* back gesture animation.
@@ -96,13 +100,12 @@
*/
@VisibleForTesting
@Nullable
- BackNavigationInfo startBackNavigation(
- IWindowFocusObserver observer, BackAnimationAdapter adapter) {
+ BackNavigationInfo startBackNavigation(@NonNull RemoteCallback navigationObserver,
+ BackAnimationAdapter adapter) {
if (!sPredictBackEnable) {
return null;
}
final WindowManagerService wmService = mWindowManagerService;
- mFocusObserver = observer;
int backType = BackNavigationInfo.TYPE_UNDEFINED;
@@ -202,9 +205,7 @@
backType = BackNavigationInfo.TYPE_CALLBACK;
}
infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
- if (mFocusObserver != null) {
- window.registerFocusObserver(mFocusObserver);
- }
+ mNavigationMonitor.startMonitor(window, navigationObserver);
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
@@ -228,10 +229,8 @@
|| currentActivity.isActivityTypeHome()
|| currentActivity.mHasSceneTransition) {
infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK);
- final WindowState finalFocusedWindow = window;
infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
- onBackNavigationDone(result, finalFocusedWindow,
- BackNavigationInfo.TYPE_CALLBACK)));
+ onBackNavigationDone(result, BackNavigationInfo.TYPE_CALLBACK)));
mLastBackType = BackNavigationInfo.TYPE_CALLBACK;
return infoBuilder.build();
}
@@ -334,16 +333,19 @@
WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
if (finalRemovedWindowContainer != null) {
final int finalBackType = backType;
- final WindowState finalFocusedWindow = window;
RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
- result, finalFocusedWindow, finalBackType));
+ result, finalBackType));
infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
}
mLastBackType = backType;
return infoBuilder.build();
}
- boolean isWaitBackTransition() {
+ boolean isMonitoringTransition() {
+ return isWaitBackTransition() || mNavigationMonitor.isMonitoring();
+ }
+
+ private boolean isWaitBackTransition() {
return mAnimationHandler.mComposed && mAnimationHandler.mWaitTransition;
}
@@ -363,11 +365,23 @@
*/
boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
ArraySet<ActivityRecord> closeApps) {
- if (!isWaitBackTransition()) {
+ if (!isMonitoringTransition()) {
return false;
}
mTmpCloseApps.addAll(closeApps);
- boolean result = false;
+ final boolean matchAnimationTargets = removeIfWaitForBackTransition(openApps, closeApps);
+ if (!matchAnimationTargets) {
+ mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
+ }
+ mTmpCloseApps.clear();
+ return matchAnimationTargets;
+ }
+
+ boolean removeIfWaitForBackTransition(ArraySet<ActivityRecord> openApps,
+ ArraySet<ActivityRecord> closeApps) {
+ if (!isWaitBackTransition()) {
+ return false;
+ }
// Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from
// mOpeningApps if there is no visibility change.
if (mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) {
@@ -386,10 +400,76 @@
closeApps.removeAt(i);
}
}
- result = true;
+ return true;
}
- mTmpCloseApps.clear();
- return result;
+ return false;
+ }
+
+ private static class NavigationMonitor {
+ // The window which triggering the back navigation.
+ private WindowState mNavigatingWindow;
+ private RemoteCallback mObserver;
+
+ void startMonitor(@NonNull WindowState window, @NonNull RemoteCallback observer) {
+ mNavigatingWindow = window;
+ mObserver = observer;
+ }
+
+ void stopMonitor() {
+ mNavigatingWindow = null;
+ mObserver = null;
+ }
+
+ boolean isMonitoring() {
+ return mNavigatingWindow != null && mObserver != null;
+ }
+
+ /**
+ * Notify focus window changed during back navigation. This will cancel the gesture for
+ * scenarios like: a system window popup, or when an activity add a new window.
+ *
+ * This method should only be used to check window-level change, otherwise it may cause
+ * misjudgment in multi-window mode. For example: in split-screen, when user is
+ * navigating on the top task, bottom task can start a new task, which will gain focus for
+ * a short time, but we should not cancel the navigation.
+ */
+ private void onFocusWindowChanged(WindowState newFocus) {
+ if (!isMonitoring() || !atSameDisplay(newFocus)) {
+ return;
+ }
+ // Keep navigating if either new focus == navigating window or null.
+ if (newFocus != null && newFocus != mNavigatingWindow
+ && (newFocus.mActivityRecord == null
+ || (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) {
+ EventLogTags.writeWmBackNaviCanceled("focusWindowChanged");
+ mObserver.sendResult(null /* result */);
+ }
+ }
+
+ /**
+ * Notify an unexpected transition has happened during back navigation.
+ */
+ private void onTransitionReadyWhileNavigate(ArrayList<WindowContainer> opening,
+ ArrayList<WindowContainer> closing) {
+ if (!isMonitoring()) {
+ return;
+ }
+ final ArrayList<WindowContainer> all = new ArrayList<>(opening);
+ all.addAll(closing);
+ for (WindowContainer app : all) {
+ if (app.hasChild(mNavigatingWindow)) {
+ EventLogTags.writeWmBackNaviCanceled("transitionHappens");
+ mObserver.sendResult(null /* result */);
+ break;
+ }
+ }
+
+ }
+
+ private boolean atSameDisplay(WindowState newFocus) {
+ final int navigatingDisplayId = mNavigatingWindow.getDisplayId();
+ return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId;
+ }
}
// For shell transition
@@ -403,8 +483,7 @@
* animations, and shouldn't join next transition.
*/
boolean containsBackAnimationTargets(Transition transition) {
- if (!mAnimationHandler.mComposed
- || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) {
+ if (!isMonitoringTransition()) {
return false;
}
final ArraySet<WindowContainer> targets = transition.mParticipants;
@@ -420,19 +499,19 @@
mTmpCloseApps.add(wc);
}
}
- final boolean result = mAnimationHandler.containsBackAnimationTargets(
- mTmpOpenApps, mTmpCloseApps);
- if (result) {
- mAnimationHandler.mOpenTransitionTargetMatch =
- mAnimationHandler.containTarget(mTmpOpenApps, true);
+ final boolean matchAnimationTargets = isWaitBackTransition()
+ && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
+ && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+ if (!matchAnimationTargets) {
+ mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
}
mTmpOpenApps.clear();
mTmpCloseApps.clear();
- return result;
+ return matchAnimationTargets;
}
boolean isMonitorTransitionTarget(WindowContainer wc) {
- if (!mAnimationHandler.mComposed || !mAnimationHandler.mWaitTransition) {
+ if (!isWaitBackTransition()) {
return false;
}
return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
@@ -951,16 +1030,13 @@
}
}
- private void onBackNavigationDone(Bundle result, WindowState focusedWindow, int backType) {
+ private void onBackNavigationDone(Bundle result, int backType) {
boolean triggerBack = result != null && result.getBoolean(
BackNavigationInfo.KEY_TRIGGER_BACK);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ "triggerBack=%b", backType, triggerBack);
- if (mFocusObserver != null) {
- focusedWindow.unregisterFocusObserver(mFocusObserver);
- mFocusObserver = null;
- }
+ mNavigationMonitor.stopMonitor();
mBackAnimationInProgress = false;
mShowWallpaper = false;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index dfa1a62..86c4e0f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3809,6 +3809,7 @@
}
getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
+ mAtmService.mBackNavigationController.onFocusChanged(newFocus);
if (imWindowChanged && oldFocus != mInputMethodWindow) {
// Focus of the input method window changed. Perform layout if needed.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a15967bb..ce3379e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2590,6 +2590,7 @@
lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ lp.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setFitInsetsTypes(0);
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
if (ActivityManager.isHighEndGfx()) {
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 031e022..594929b 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -66,6 +66,8 @@
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
+# Back navigation.
+31100 wm_back_navi_canceled (Reason|3)
# IME surface parent is updated.
32003 imf_update_ime_parent (surface name|3)
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 8cbd553..3cec3aa 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1225,7 +1225,7 @@
// Clear last paused activity if focused root task changed while sleeping, so that the
// top activity of current focused task can be resumed.
- if (mDisplayContent.isSleeping()) {
+ if (mDisplayContent.isSleeping() && currentFocusedTask != null) {
currentFocusedTask.clearLastPausedActivity();
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index 3d504ef..9c50a5a 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -49,11 +49,14 @@
/** Represents the results of a given query into the registry. */
public static final class FilterResult {
final String mPackageName;
+ final String mFlattenedRequest;
final List<CredentialEntry> mCredentialEntries;
private FilterResult(String packageName,
+ String flattenedRequest,
List<CredentialEntry> credentialEntries) {
mPackageName = packageName;
+ mFlattenedRequest = flattenedRequest;
mCredentialEntries = credentialEntries;
}
}
@@ -133,12 +136,13 @@
/** Returns package names and entries of a CredentialProviders that can satisfy a given
* {@link CredentialDescription}. */
public Set<FilterResult> getFilteredResultForProvider(String packageName,
- List<String> flatRequestStrings) {
+ String flatRequestStrings) {
Set<FilterResult> result = new HashSet<>();
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
for (CredentialDescription containedDescription: currentSet) {
- if (flatRequestStrings.contains(containedDescription.getFlattenedRequestString())) {
- result.add(new FilterResult(packageName, containedDescription
+ if (flatRequestStrings.equals(containedDescription.getFlattenedRequestString())) {
+ result.add(new FilterResult(packageName,
+ containedDescription.getFlattenedRequestString(), containedDescription
.getCredentialEntries()));
}
}
@@ -147,13 +151,15 @@
/** Returns package names of CredentialProviders that can satisfy a given
* {@link CredentialDescription}. */
- public Set<String> getMatchingProviders(Set<String> flatRequestString) {
- Set<String> result = new HashSet<>();
+ public Set<FilterResult> getMatchingProviders(Set<String> flatRequestString) {
+ Set<FilterResult> result = new HashSet<>();
for (String packageName: mCredentialDescriptions.keySet()) {
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
for (CredentialDescription containedDescription : currentSet) {
if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) {
- result.add(packageName);
+ result.add(new FilterResult(packageName,
+ containedDescription.getFlattenedRequestString(), containedDescription
+ .getCredentialEntries()));
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index f3bbfb5..7a4e7df 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.credentials;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
import static android.content.Context.CREDENTIAL_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -58,6 +59,7 @@
import android.service.credentials.CredentialProviderInfo;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -66,6 +68,7 @@
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -274,24 +277,25 @@
// to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
GetRequestSession session,
- List<String> requestOptions,
- Set<String> activeCredentialContainers) {
+ Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
+ activeCredentialContainers) {
List<ProviderSession> providerSessions = new ArrayList<>();
- // Invoke all services of a user to initiate a provider session
- for (String packageName : activeCredentialContainers) {
+ for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
+ activeCredentialContainers) {
providerSessions.add(
ProviderRegistryGetSession.createNewSession(
mContext,
UserHandle.getCallingUserId(),
session,
- packageName,
- requestOptions));
+ result.second.mPackageName,
+ result.first));
}
return providerSessions;
}
@NonNull
- private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
+ private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
+ getFilteredResultFromRegistry(List<CredentialOption> options) {
// Session for active/provisioned credential descriptions;
CredentialDescriptionRegistry registry =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
@@ -307,7 +311,22 @@
.collect(Collectors.toSet());
// All requested credential descriptions based on the given request.
- return registry.getMatchingProviders(requestedCredentialDescriptions);
+ Set<CredentialDescriptionRegistry.FilterResult> filterResults =
+ registry.getMatchingProviders(requestedCredentialDescriptions);
+
+ Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>> result =
+ new HashSet<>();
+
+ for (CredentialDescriptionRegistry.FilterResult filterResult: filterResults) {
+ for (CredentialOption credentialOption: options) {
+ if (filterResult.mFlattenedRequest.equals(credentialOption
+ .getCredentialRetrievalData()
+ .getString(CredentialOption.FLATTENED_REQUEST))) {
+ result.add(new Pair<>(credentialOption, filterResult));
+ }
+ }
+ }
+ return result;
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -342,21 +361,22 @@
int userId,
@Nullable String origin) {
final PackageInfo packageInfo;
- String actualPackageName = origin == null ? realPackageName : origin;
+ CallingAppInfo callingAppInfo;
try {
packageInfo =
getContext()
.getPackageManager()
.getPackageInfoAsUser(
- actualPackageName,
+ realPackageName,
PackageManager.PackageInfoFlags.of(
PackageManager.GET_SIGNING_CERTIFICATES),
userId);
+ callingAppInfo = new CallingAppInfo(realPackageName, packageInfo.signingInfo, origin);
} catch (PackageManager.NameNotFoundException e) {
Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
- return new CallingAppInfo(actualPackageName, null);
+ callingAppInfo = new CallingAppInfo(realPackageName, null, origin);
}
- return new CallingAppInfo(actualPackageName, packageInfo.signingInfo);
+ return callingAppInfo;
}
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@@ -368,9 +388,15 @@
Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ if (request.getOrigin() != null) {
+ // Check privileged permissions
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+ }
+
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
+
// New request session, scoped for this request only.
final GetRequestSession session =
new GetRequestSession(
@@ -379,43 +405,13 @@
callingUid,
callback,
request,
- constructCallingAppInfo(callingPackage, userId, null),
+ constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
CancellationSignal.fromTransport(cancelTransport));
processGetCredential(request, callback, session);
return cancelTransport;
}
- public ICancellationSignal executeGetCredentialWithOrigin(
- GetCredentialRequest request,
- IGetCredentialCallback callback,
- final String callingPackage,
- final String origin) {
- Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
- // Check privileged permissions
- mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
-
- final int userId = UserHandle.getCallingUserId();
- final int callingUid = Binder.getCallingUid();
- enforceCallingPackage(callingPackage, callingUid);
-
- // New request session, scoped for this request only.
- final GetRequestSession session =
- new GetRequestSession(
- getContext(),
- userId,
- callingUid,
- callback,
- request,
- constructCallingAppInfo(callingPackage, userId, origin),
- CancellationSignal.fromTransport(cancelTransport));
-
- processGetCredential(request, callback, session);
- return cancelTransport;
- }
-
private void processGetCredential(
GetCredentialRequest request,
IGetCredentialCallback callback,
@@ -452,15 +448,6 @@
List<ProviderSession> sessionsWithoutRemoteService =
initiateProviderSessionsWithActiveContainers(
session,
- optionsThatRequireActiveCredentials.stream()
- .map(
- getCredentialOption ->
- getCredentialOption
- .getCredentialRetrievalData()
- .getString(
- CredentialOption
- .FLATTENED_REQUEST))
- .collect(Collectors.toList()),
getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
List<ProviderSession> sessionsWithRemoteService =
@@ -510,6 +497,11 @@
+ callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ if (request.getOrigin() != null) {
+ // Check privileged permissions
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+ }
+
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
@@ -522,43 +514,13 @@
callingUid,
request,
callback,
- constructCallingAppInfo(callingPackage, userId, null),
+ constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
CancellationSignal.fromTransport(cancelTransport));
processCreateCredential(request, callback, session);
return cancelTransport;
}
- public ICancellationSignal executeCreateCredentialWithOrigin(
- CreateCredentialRequest request,
- ICreateCredentialCallback callback,
- String callingPackage,
- String origin) {
- Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
- // Check privileged permissions
- mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
-
- final int userId = UserHandle.getCallingUserId();
- final int callingUid = Binder.getCallingUid();
- enforceCallingPackage(callingPackage, callingUid);
-
- // New request session, scoped for this request only.
- final CreateRequestSession session =
- new CreateRequestSession(
- getContext(),
- userId,
- callingUid,
- request,
- callback,
- constructCallingAppInfo(callingPackage, userId, origin),
- CancellationSignal.fromTransport(cancelTransport));
-
- processCreateCredential(request, callback, session);
- return cancelTransport;
- }
-
private void processCreateCredential(
CreateCredentialRequest request,
ICreateCredentialCallback callback,
@@ -670,7 +632,8 @@
}
// Send an intent to the UI that we have new enabled providers.
- getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent());
+ getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
+ LAUNCH_CREDENTIAL_SELECTOR);
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 5af8080..a57cb5f 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -23,7 +23,6 @@
import android.content.Intent;
import android.credentials.CredentialOption;
import android.credentials.GetCredentialException;
-import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
@@ -49,7 +48,7 @@
*
* @hide
*/
-public class ProviderRegistryGetSession extends ProviderSession<GetCredentialRequest,
+public class ProviderRegistryGetSession extends ProviderSession<CredentialOption,
Set<CredentialDescriptionRegistry.FilterResult>> {
private static final String TAG = "ProviderRegistryGetSession";
@@ -62,15 +61,14 @@
@UserIdInt int userId,
@NonNull GetRequestSession getRequestSession,
@NonNull String credentialProviderPackageName,
- @NonNull List<String> requestOptions) {
+ @NonNull CredentialOption requestOption) {
return new ProviderRegistryGetSession(
context,
userId,
getRequestSession,
- getRequestSession.mClientRequest,
getRequestSession.mClientAppInfo,
credentialProviderPackageName,
- requestOptions);
+ requestOption);
}
@NonNull
@@ -82,24 +80,22 @@
@NonNull
private final String mCredentialProviderPackageName;
@NonNull
- private final GetRequestSession mGetRequestSession;
- @NonNull
- private final List<String> mRequestOptions;
+ private final String mFlattenedRequestOptionString;
private List<CredentialEntry> mCredentialEntries;
protected ProviderRegistryGetSession(@NonNull Context context,
@NonNull int userId,
@NonNull GetRequestSession session,
- @NonNull GetCredentialRequest request,
@NonNull CallingAppInfo callingAppInfo,
@NonNull String servicePackageName,
- @NonNull List<String> requestOptions) {
- super(context, null, request, session, userId, null);
- mGetRequestSession = session;
+ @NonNull CredentialOption requestOption) {
+ super(context, null, requestOption, session, userId, null);
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
mCredentialProviderPackageName = servicePackageName;
- mRequestOptions = requestOptions;
+ mFlattenedRequestOptionString = requestOption
+ .getCredentialRetrievalData()
+ .getString(CredentialOption.FLATTENED_REQUEST);
}
private List<Entry> prepareUiCredentialEntries(
@@ -114,23 +110,18 @@
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(),
- setUpFillInIntent(credentialEntry.getType())));
+ setUpFillInIntent()));
}
return credentialUiEntries;
}
- private Intent setUpFillInIntent(String type) {
+ private Intent setUpFillInIntent() {
Intent intent = new Intent();
- for (CredentialOption option : mProviderRequest.getCredentialOptions()) {
- if (option.getType().equals(type)) {
- intent.putExtra(
- CredentialProviderService
- .EXTRA_GET_CREDENTIAL_REQUEST,
- new android.service.credentials.GetCredentialRequest(
- mCallingAppInfo, option));
- return intent;
- }
- }
+ intent.putExtra(
+ CredentialProviderService
+ .EXTRA_GET_CREDENTIAL_REQUEST,
+ new android.service.credentials.GetCredentialRequest(
+ mCallingAppInfo, mProviderRequest));
return intent;
}
@@ -176,11 +167,6 @@
private void onCredentialEntrySelected(CredentialEntry credentialEntry,
ProviderPendingIntentResponse providerPendingIntentResponse) {
- if (!mCredentialEntries.contains(credentialEntry)) {
- invokeCallbackWithError("",
- "");
- }
-
if (providerPendingIntentResponse != null) {
// Check if pending intent has an error
GetCredentialException exception = maybeGetPendingIntentException(
@@ -228,13 +214,13 @@
protected void invokeSession() {
mProviderResponse = mCredentialDescriptionRegistry
.getFilteredResultForProvider(mCredentialProviderPackageName,
- mRequestOptions);
+ mFlattenedRequestOptionString);
mCredentialEntries = mProviderResponse.stream().flatMap(
(Function<CredentialDescriptionRegistry.FilterResult,
Stream<CredentialEntry>>) filterResult
-> filterResult.mCredentialEntries.stream())
.collect(Collectors.toList());
- setStatus(Status.CREDENTIALS_RECEIVED);
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
// TODO(use metric later)
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java
new file mode 100644
index 0000000..a329f5a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.base.Optional;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Table;
+import com.google.common.truth.BigDecimalSubject;
+import com.google.common.truth.BooleanSubject;
+import com.google.common.truth.ClassSubject;
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.DoubleSubject;
+import com.google.common.truth.Expect;
+import com.google.common.truth.FloatSubject;
+import com.google.common.truth.GuavaOptionalSubject;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.LongSubject;
+import com.google.common.truth.MapSubject;
+import com.google.common.truth.MultimapSubject;
+import com.google.common.truth.MultisetSubject;
+import com.google.common.truth.ObjectArraySubject;
+import com.google.common.truth.PrimitiveBooleanArraySubject;
+import com.google.common.truth.PrimitiveByteArraySubject;
+import com.google.common.truth.PrimitiveCharArraySubject;
+import com.google.common.truth.PrimitiveDoubleArraySubject;
+import com.google.common.truth.PrimitiveFloatArraySubject;
+import com.google.common.truth.PrimitiveIntArraySubject;
+import com.google.common.truth.PrimitiveLongArraySubject;
+import com.google.common.truth.PrimitiveShortArraySubject;
+import com.google.common.truth.StandardSubjectBuilder;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.common.truth.TableSubject;
+import com.google.common.truth.ThrowableSubject;
+
+import org.junit.Rule;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+// NOTE: it could be a more generic AbstractTruthTestCase that provide similar methods
+// for assertThat() / assertWithMessage(), but then we'd need to remove all static import imports
+// from classes that indirectly extend it.
+/**
+ * Base class to make it easier to use {@code Truth} {@link Expect} assertions.
+ */
+public abstract class ExpectableTestCase {
+
+ @Rule
+ public final Expect mExpect = Expect.create();
+
+ protected final StandardSubjectBuilder expectWithMessage(String msg) {
+ return mExpect.withMessage(msg);
+ }
+
+ protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
+ return mExpect.withMessage(format, args);
+ }
+
+ protected final <ComparableT extends Comparable<?>> ComparableSubject<ComparableT> expectThat(
+ ComparableT actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final BigDecimalSubject expectThat(BigDecimal actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final Subject expectThat(Object actual) {
+ return mExpect.that(actual);
+ }
+
+ @GwtIncompatible("ClassSubject.java")
+ protected final ClassSubject expectThat(Class<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final ThrowableSubject expectThat(Throwable actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final LongSubject expectThat(Long actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final DoubleSubject expectThat(Double actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final FloatSubject expectThat(Float actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final IntegerSubject expectThat(Integer actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final BooleanSubject expectThat(Boolean actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final StringSubject expectThat(String actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final IterableSubject expectThat(Iterable<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final <T> ObjectArraySubject<T> expectThat(T[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveBooleanArraySubject expectThat(boolean[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveShortArraySubject expectThat(short[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveIntArraySubject expectThat(int[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveLongArraySubject expectThat(long[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveCharArraySubject expectThat(char[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveByteArraySubject expectThat(byte[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveFloatArraySubject expectThat(float[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveDoubleArraySubject expectThat(double[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final GuavaOptionalSubject expectThat(Optional<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final MapSubject expectThat(Map<?, ?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final MultimapSubject expectThat(Multimap<?, ?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final MultisetSubject expectThat(Multiset<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final TableSubject expectThat(Table<?, ?, ?> actual) {
+ return mExpect.that(actual);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
new file mode 100644
index 0000000..881dd50
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.internal.util.Preconditions;
+import com.android.modules.utils.testing.StaticMockFixture;
+import com.android.modules.utils.testing.StaticMockFixtureRule;
+
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Rule to make it easier to use Extended Mockito.
+ *
+ * <p>It's derived from {@link StaticMockFixtureRule}, with the additional features:
+ *
+ * <ul>
+ * <li>Easier to define which classes must be statically mocked or spied
+ * <li>Automatically starts mocking (so tests don't need a mockito runner or rule)
+ * <li>Automatically clears the inlined mocks at the end (to avoid OOM)
+ * <li>Allows other customization like strictness
+ * </ul>
+ */
+public final class ExtendedMockitoRule extends StaticMockFixtureRule {
+
+ private static final String TAG = ExtendedMockitoRule.class.getSimpleName();
+
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final Object mTestClassInstance;
+ private final Strictness mStrictness;
+
+ private ExtendedMockitoRule(Builder builder) {
+ super(() -> new SimpleStatickMockFixture(builder.mMockedStaticClasses,
+ builder.mSpiedStaticClasses, builder.mDynamicSessionBuilderConfigurator,
+ builder.mAfterSessionFinishedCallback));
+ mTestClassInstance = builder.mTestClassInstance;
+ mStrictness = builder.mStrictness;
+ if (VERBOSE) {
+ Log.v(TAG, "strictness=" + mStrictness + ", testClassInstance" + mTestClassInstance
+ + ", mockedStaticClasses=" + builder.mMockedStaticClasses
+ + ", spiedStaticClasses=" + builder.mSpiedStaticClasses
+ + ", dynamicSessionBuilderConfigurator="
+ + builder.mDynamicSessionBuilderConfigurator
+ + ", afterSessionFinishedCallback=" + builder.mAfterSessionFinishedCallback);
+ }
+ }
+
+ @Override
+ public StaticMockitoSessionBuilder getSessionBuilder() {
+ StaticMockitoSessionBuilder sessionBuilder = super.getSessionBuilder();
+ if (mStrictness != null) {
+ if (VERBOSE) {
+ Log.v(TAG, "Setting strictness to " + mStrictness + " on " + sessionBuilder);
+ }
+ sessionBuilder.strictness(mStrictness);
+ }
+ return sessionBuilder.initMocks(mTestClassInstance);
+ }
+
+ public static final class Builder {
+ private final Object mTestClassInstance;
+ private @Nullable Strictness mStrictness;
+ private final List<Class<?>> mMockedStaticClasses = new ArrayList<>();
+ private final List<Class<?>> mSpiedStaticClasses = new ArrayList<>();
+ private @Nullable Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
+ private @Nullable Runnable mAfterSessionFinishedCallback;
+
+ public Builder(Object testClassInstance) {
+ mTestClassInstance = Objects.requireNonNull(testClassInstance);
+ }
+
+ public Builder setStrictness(Strictness strictness) {
+ mStrictness = Objects.requireNonNull(strictness);
+ return this;
+ }
+
+ public Builder mockStatic(Class<?> clazz) {
+ Objects.requireNonNull(clazz);
+ Preconditions.checkState(!mMockedStaticClasses.contains(clazz),
+ "class %s already mocked", clazz);
+ mMockedStaticClasses.add(clazz);
+ return this;
+ }
+
+ public Builder spyStatic(Class<?> clazz) {
+ Objects.requireNonNull(clazz);
+ Preconditions.checkState(!mSpiedStaticClasses.contains(clazz),
+ "class %s already spied", clazz);
+ mSpiedStaticClasses.add(clazz);
+ return this;
+ }
+
+ public Builder dynamiclyConfigureSessionBuilder(
+ Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator) {
+ mDynamicSessionBuilderConfigurator = Objects
+ .requireNonNull(dynamicSessionBuilderConfigurator);
+ return this;
+ }
+
+ public Builder afterSessionFinished(Runnable runnable) {
+ mAfterSessionFinishedCallback = Objects.requireNonNull(runnable);
+ return this;
+ }
+
+ public ExtendedMockitoRule build() {
+ return new ExtendedMockitoRule(this);
+ }
+ }
+
+ private static final class SimpleStatickMockFixture implements StaticMockFixture {
+
+ private final List<Class<?>> mMockedStaticClasses;
+ private final List<Class<?>> mSpiedStaticClasses;
+ @Nullable
+ private final Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
+ @Nullable
+ private final Runnable mAfterSessionFinishedCallback;
+
+ private SimpleStatickMockFixture(List<Class<?>> mockedStaticClasses,
+ List<Class<?>> spiedStaticClasses,
+ @Nullable Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator,
+ @Nullable Runnable afterSessionFinishedCallback) {
+ mMockedStaticClasses = mockedStaticClasses;
+ mSpiedStaticClasses = spiedStaticClasses;
+ mDynamicSessionBuilderConfigurator = dynamicSessionBuilderConfigurator;
+ mAfterSessionFinishedCallback = afterSessionFinishedCallback;
+ }
+
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ mMockedStaticClasses.forEach((c) -> sessionBuilder.mockStatic(c));
+ mSpiedStaticClasses.forEach((c) -> sessionBuilder.spyStatic(c));
+ if (mDynamicSessionBuilderConfigurator != null) {
+ mDynamicSessionBuilderConfigurator.visit(sessionBuilder);
+ }
+ return sessionBuilder;
+ }
+
+ @Override
+ public void setUpMockBehaviors() {
+ }
+
+ @Override
+ public void tearDown() {
+ try {
+ if (mAfterSessionFinishedCallback != null) {
+ mAfterSessionFinishedCallback.run();
+ }
+ } finally {
+ if (VERBOSE) {
+ Log.v(TAG, "calling Mockito.framework().clearInlineMocks()");
+ }
+ // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
+ // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
+ Mockito.framework().clearInlineMocks();
+ }
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
deleted file mode 100644
index 48483a1..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import android.annotation.Nullable;
-import android.util.Log;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-
-import com.google.common.truth.Expect;
-import com.google.common.truth.StandardSubjectBuilder;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.mockito.Mockito;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.lang.reflect.Constructor;
-
-/**
- * Base class to make it easier to write tests that uses {@code ExtendedMockito}.
- *
- */
-public abstract class ExtendedMockitoTestCase {
-
- private static final String TAG = ExtendedMockitoTestCase.class.getSimpleName();
-
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- /**
- * Number of invocations, used to force a failure on {@link #forceFailure(int, Class, String)}.
- */
- private static int sInvocationsCounter;
-
- /**
- * Sessions follow the "Highlander Rule": There can be only one!
- *
- * <p>So, we keep track of that and force-close it if needed.
- */
- @Nullable
- private static MockitoSession sHighlanderSession;
-
- /**
- * Points to where the current session was created.
- */
- private static Exception sSessionCreationLocation;
-
- private MockitoSession mSession;
-
- protected final Expect mExpect = Expect.create();
- protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
-
- @Rule
- public final RuleChain mTwoRingsOfPowerAndOneChainToRuleThemAll = RuleChain
- .outerRule(mDumpableDumperRule)
- .around(mExpect);
-
- public ExtendedMockitoTestCase() {
- sInvocationsCounter++;
- }
-
- @Before
- public final void startSession() {
- if (VERBOSE) {
- Log.v(TAG, "startSession() for " + getTestName() + " on thread "
- + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
- }
- createSessionLocation();
- finishHighlanderSessionIfNeeded("startSession()");
- StaticMockitoSessionBuilder builder = mockitoSession()
- .initMocks(this)
- .strictness(getSessionStrictness());
- initializeSession(builder);
- sHighlanderSession = mSession = builder.startMocking();
- }
-
- private void createSessionLocation() {
- try {
- sSessionCreationLocation = new Exception(getTestName());
- } catch (Exception e) {
- // Better safe than sorry...
- Log.e(TAG, "Could not create sSessionCreationLocation with " + getTestName()
- + " on thread " + Thread.currentThread(), e);
- sSessionCreationLocation = e;
- }
- }
-
- /**
- * Gets the session strictness.
- *
- * @return {@link Strictness.LENIENT} by default; subclasses can override.
- */
- protected Strictness getSessionStrictness() {
- return Strictness.LENIENT;
- }
-
- /**
- * Initializes the mockito session.
- *
- * <p>Typically used to define which classes should have static methods mocked or spied.
- */
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- if (VERBOSE) {
- Log.v(TAG, "initializeSession()");
- }
- }
-
- @After
- public final void finishSession() throws Exception {
- if (false) { // For obvious reasons, should NEVER be merged as true
- forceFailure(1, RuntimeException.class, "to simulate an unfinished session");
- }
-
- // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
- // statements), otherwise it would cause failures on future tests as mockito
- // cannot start a session when a previous one is not finished
- try {
- if (VERBOSE) {
- Log.v(TAG, "finishSession() for " + getTestName() + " on thread "
- + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
-
- }
- } finally {
- sHighlanderSession = null;
- finishSessionMocking();
- afterSessionFinished();
- }
- }
-
- /**
- * Called after the mockito session was finished
- *
- * <p>This method should be used by subclasses that MUST do their cleanup after the session is
- * finished (as methods marked with {@link After} in the subclasses would be called BEFORE
- * that).
- */
- protected void afterSessionFinished() {
- if (VERBOSE) {
- Log.v(TAG, "afterSessionFinished()");
- }
- }
-
- private void finishSessionMocking() {
- if (mSession == null) {
- Log.w(TAG, getClass().getSimpleName() + ".finishSession(): no session");
- return;
- }
- try {
- mSession.finishMocking();
- } finally {
- // Shouldn't need to set mSession to null as JUnit always instantiate a new object,
- // but it doesn't hurt....
- mSession = null;
- // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
- // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
- Mockito.framework().clearInlineMocks();
- }
- }
-
- private void finishHighlanderSessionIfNeeded(String where) {
- if (sHighlanderSession == null) {
- if (VERBOSE) {
- Log.v(TAG, "finishHighlanderSessionIfNeeded(): sHighlanderSession already null");
- }
- return;
- }
-
- if (sSessionCreationLocation != null) {
- if (VERBOSE) {
- Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
- + "created at", sSessionCreationLocation);
- } else {
- Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
- + "created at " + sSessionCreationLocation);
- }
- } else {
- Log.e(TAG, where + ": There can be only one! Closing unfinished session created at "
- + "unknown location");
- }
- try {
- sHighlanderSession.finishMocking();
- } catch (Throwable t) {
- if (VERBOSE) {
- Log.e(TAG, "Failed to close unfinished session on " + getTestName(), t);
- } else {
- Log.e(TAG, "Failed to close unfinished session on " + getTestName() + ": " + t);
- }
- } finally {
- if (VERBOSE) {
- Log.v(TAG, "Resetting sHighlanderSession at finishHighlanderSessionIfNeeded()");
- }
- sHighlanderSession = null;
- }
- }
-
- /**
- * Forces a failure at the given invocation of a test method by throwing an exception.
- */
- protected final <T extends Throwable> void forceFailure(int invocationCount,
- Class<T> failureClass, String reason) throws T {
- if (sInvocationsCounter != invocationCount) {
- Log.d(TAG, "forceFailure(" + invocationCount + "): no-op on invocation #"
- + sInvocationsCounter);
- return;
- }
- String message = "Throwing on invocation #" + sInvocationsCounter + ": " + reason;
- Log.e(TAG, message);
- T throwable;
- try {
- Constructor<T> constructor = failureClass.getConstructor(String.class);
- throwable = constructor.newInstance("Throwing on invocation #" + sInvocationsCounter
- + ": " + reason);
- } catch (Exception e) {
- throw new IllegalArgumentException("Could not create exception of class " + failureClass
- + " using msg='" + message + "' as constructor");
- }
- throw throwable;
- }
-
- protected final @Nullable String getTestName() {
- return mDumpableDumperRule.getTestName();
- }
-
- protected final StandardSubjectBuilder expectWithMessage(String msg) {
- return mExpect.withMessage(msg);
- }
-
- protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
- return mExpect.withMessage(format, args);
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/Visitor.java b/services/tests/mockingservicestests/src/com/android/server/Visitor.java
new file mode 100644
index 0000000..447910e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/Visitor.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+/**
+ * Generic visitor.
+ */
+public interface Visitor<V> {
+ void visit(V visitee);
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 9dd2f82..b395f42 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -159,7 +159,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.MockedVoidMethod;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
@@ -168,7 +167,7 @@
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.SystemService;
@@ -182,6 +181,7 @@
import libcore.util.EmptyArray;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -204,7 +204,7 @@
@Presubmit
@SuppressWarnings("GuardedBy") // This test enforces synchronous behavior.
@RunWith(AndroidJUnit4.class)
-public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase {
+public final class AlarmManagerServiceTest {
private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
private static final int SYSTEM_UI_UID = 12345;
private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
@@ -411,14 +411,9 @@
}
}
- @Override
- protected Strictness getSessionStrictness() {
- return Strictness.WARN;
- }
-
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .setStrictness(Strictness.WARN)
.spyStatic(ActivityManager.class)
.mockStatic(CompatChanges.class)
.spyStatic(DateFormat.class)
@@ -431,8 +426,10 @@
.mockStatic(ServiceManager.class)
.mockStatic(Settings.Global.class)
.mockStatic(SystemProperties.class)
- .spyStatic(UserHandle.class);
- }
+ .spyStatic(UserHandle.class)
+ .afterSessionFinished(
+ () -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
+ .build();
@Before
public final void setUp() {
@@ -3805,9 +3802,4 @@
mListener.removeListenerAlarmsForCachedUid(TEST_CALLING_UID_2);
assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
}
-
- @Override
- public void afterSessionFinished() {
- LocalServices.removeServiceForTest(AlarmManagerInternal.class);
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
index e01a9a9..9ceb5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -31,11 +31,11 @@
import android.util.Log;
import android.view.Display;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.am.ActivityManagerService.Injector;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -45,7 +45,7 @@
* Run as {@code atest
* FrameworksMockingServicesTests:com.android.server.am.ActivityManagerServiceInjectorTest}
*/
-public final class ActivityManagerServiceInjectorTest extends ExtendedMockitoTestCase {
+public final class ActivityManagerServiceInjectorTest {
private static final String TAG = ActivityManagerServiceInjectorTest.class.getSimpleName();
@@ -63,10 +63,10 @@
when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
}
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.spyStatic(UserManager.class);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(UserManager.class)
+ .build();
@Test
public void testGetDisplayIdsForStartingBackgroundUsers_notSupported() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index dcdee37..95c2ed2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -78,18 +78,15 @@
import androidx.test.filters.SmallTest;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.am.BroadcastQueueTest.SyncBarrier;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
@@ -98,8 +95,7 @@
import java.util.List;
@SmallTest
-@RunWith(MockitoJUnitRunner.class)
-public class BroadcastQueueModernImplTest extends ExtendedMockitoTestCase {
+public final class BroadcastQueueModernImplTest {
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
@@ -118,15 +114,13 @@
BroadcastProcessQueue mHead;
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.spyStatic(FrameworkStatsLog.class);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(FrameworkStatsLog.class)
+ .build();
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index f8cfdf1..ec9e5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -40,9 +40,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.modules.utils.testing.TestableDeviceConfig;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
@@ -69,7 +68,7 @@
* atest FrameworksMockingServicesTests:CachedAppOptimizerTest
*/
@Presubmit
-public final class CachedAppOptimizerTest extends ExtendedMockitoTestCase {
+public final class CachedAppOptimizerTest {
private ServiceThread mThread;
@@ -93,10 +92,11 @@
public final ApplicationExitInfoTest.ServiceThreadRule
mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- mDeviceConfig.setUpMockedClasses(builder);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .dynamiclyConfigureSessionBuilder(
+ sessionBuilder -> mDeviceConfig.setUpMockedClasses(sessionBuilder))
+ .build();
@Before
public void setUp() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index 9f3bc33..b214787 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -29,7 +29,7 @@
import android.util.Log;
import android.util.SparseArray;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExpectableTestCase;
import libcore.io.Streams;
@@ -44,7 +44,7 @@
import java.util.Objects;
/** This class contains unit tests for the {@link CpuInfoReader}. */
-public final class CpuInfoReaderTest extends ExtendedMockitoTestCase {
+public final class CpuInfoReaderTest extends ExpectableTestCase {
private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
private static final String VALID_CPUSET_DIR = "valid_cpuset";
@@ -426,7 +426,7 @@
.isNull();
}
- private static void compareCpuInfos(String message,
+ private void compareCpuInfos(String message,
SparseArray<CpuInfoReader.CpuInfo> expected,
SparseArray<CpuInfoReader.CpuInfo> actual) {
assertWithMessage("%s. Total CPU infos", message).that(actual.size())
@@ -435,7 +435,7 @@
int cpuCoreId = expected.keyAt(i);
CpuInfoReader.CpuInfo expectedCpuInfo = expected.valueAt(i);
CpuInfoReader.CpuInfo actualCpuInfo = actual.get(cpuCoreId);
- assertWithMessage("%s. Core %d's CPU info", message, cpuCoreId).that(actualCpuInfo)
+ expectWithMessage("%s. Core %s's CPU info", message, cpuCoreId).that(actualCpuInfo)
.isEqualTo(expectedCpuInfo);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 7ab1363..49a2cc6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -32,16 +32,16 @@
import android.os.Looper;
import android.os.ServiceManager;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase {
+public final class CpuMonitorServiceTest {
private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
.addThreshold(30).addThreshold(70).build();
@@ -56,10 +56,10 @@
private HandlerExecutor mHandlerExecutor;
private CpuMonitorInternal mLocalService;
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.mockStatic(ServiceManager.class);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(ServiceManager.class)
+ .build();
@Before
public void setUp() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1ed2f78..564893c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -40,9 +40,8 @@
import androidx.test.annotation.UiThreadTest;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.internal.widget.LockSettingsInternal;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerService.UserData;
@@ -50,13 +49,14 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
/**
* Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
*/
-public final class UserManagerServiceTest extends ExtendedMockitoTestCase {
+public final class UserManagerServiceTest {
private static final String TAG = UserManagerServiceTest.class.getSimpleName();
@@ -84,6 +84,13 @@
*/
private static final int PROFILE_USER_ID = 643;
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(UserManager.class)
+ .spyStatic(LocalServices.class)
+ .mockStatic(Settings.Global.class)
+ .build();
+
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -109,14 +116,6 @@
*/
private UserManagerInternal mUmi;
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder
- .spyStatic(UserManager.class)
- .spyStatic(LocalServices.class)
- .mockStatic(Settings.Global.class);
- }
-
@Before
@UiThreadTest // Needed to initialize main handler
public void setFixtures() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
index 38cf634..9aa53db 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -20,8 +20,6 @@
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_ALREADY_VISIBLE;
-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;
@@ -128,7 +126,8 @@
listener.verify();
}
- /* TODO: re-add
+
+ /* TODO(b/261538337): re-add after the reverted CL is merged again
@Test
public void
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 5176d68..1bf921c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -45,7 +45,8 @@
import android.util.Log;
import com.android.internal.util.Preconditions;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.DumpableDumperRule;
+import com.android.server.ExpectableTestCase;
import org.junit.Before;
import org.junit.Test;
@@ -64,7 +65,7 @@
* is visible, display associated to the user, etc...) for each scenario (full user started on fg,
* profile user started on bg, etc...).
*/
-abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
+abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase {
private static final String TAG = UserVisibilityMediatorTestCase.class.getSimpleName();
@@ -125,6 +126,8 @@
mBackgroundUserOnDefaultDisplayAllowed = backgroundUserOnDefaultDisplayAllowed;
}
+ protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
+
@Before
public final void setFixtures() {
mHandler = Handler.getMain();
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 4249405..503e579 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -284,7 +284,7 @@
verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
eq(mA11yms), eq(mA11yms.getTraceManager()),
- eq(mMockWindowManagerService), eq(mMockA11yWindowManager));
+ eq(mMockWindowManagerService));
}
@SmallTest
@@ -296,7 +296,7 @@
assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
- any(), any(), any(), any());
+ any(), any(), any());
}
@SmallTest
@@ -305,7 +305,7 @@
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
- any(), any(), any(), any());
+ any(), any(), any());
}
@SmallTest
@@ -314,7 +314,7 @@
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.INVALID_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
- any(), any(), any(), any());
+ any(), any(), any());
}
@SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 51d3bae..306ce4d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -44,6 +44,8 @@
import android.graphics.PointF;
import android.os.Handler;
import android.os.Message;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.testing.TestableContext;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -507,6 +509,91 @@
verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
}
+ @Test
+ public void testTransitToPanningState_scaleDifferenceOverThreshold_startDetecting() {
+ final float scale = 2.0f;
+ final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+ .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ final float persistedScale = (1.0f + threshold) * scale + 1.0f;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+
+ assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
+ @Test
+ public void testTransitToPanningState_scaleDifferenceLessThanThreshold_doNotDetect() {
+ final float scale = 2.0f;
+ final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+ .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ final float persistedScale = (1.0f + threshold) * scale - 0.1f;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+
+ assertFalse(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
+ @Test
+ public void testPanningScaleToPersistedScale_detecting_vibrateAndClear() {
+ Vibrator vibrator = mock(Vibrator.class);
+ mContext.addMockSystemService(Vibrator.class, vibrator);
+
+ mMgh.mPanningScalingState.mDetectingPassPersistedScale = true;
+
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
+
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+ mMgh.mPanningScalingState.setScaleAndClearIfNeeded(persistedScale, DEFAULT_X, DEFAULT_Y);
+
+ verify(vibrator).vibrate(any(VibrationEffect.class));
+ assertFalse(mMgh.mPanningScalingState.mScaling);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
+ @Test
+ public void testPanningScaleOverThreshold_notDetecting_startDetecting() {
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
+
+ mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+
+ final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+ .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ final float scale = (1.0f + threshold) * persistedScale + 1.0f;
+ mMgh.mPanningScalingState.setScaleAndClearIfNeeded(scale, DEFAULT_X, DEFAULT_Y);
+
+ assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
private void assertActionsInOrder(List<MotionEvent> actualEvents,
List<Integer> expectedActions) {
assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 0cd50f0..cc6f7c2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -35,6 +35,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -67,7 +68,11 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.hardware.Sensor;
+import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.IInputManager;
import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
@@ -144,6 +149,7 @@
private static final String DEVICE_NAME_3 = "device name 3";
private static final int DISPLAY_ID_1 = 2;
private static final int DISPLAY_ID_2 = 3;
+ private static final int NON_EXISTENT_DISPLAY_ID = 42;
private static final int DEVICE_OWNER_UID_1 = 50;
private static final int DEVICE_OWNER_UID_2 = 51;
private static final int UID_1 = 0;
@@ -162,6 +168,8 @@
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private static final int VIRTUAL_DEVICE_ID_1 = 42;
private static final int VIRTUAL_DEVICE_ID_2 = 43;
+ private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG =
+ new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build();
private static final VirtualDpadConfig DPAD_CONFIG =
new VirtualDpadConfig.Builder()
.setVendorId(VENDOR_ID)
@@ -221,6 +229,8 @@
@Mock
private DisplayManagerInternal mDisplayManagerInternalMock;
@Mock
+ private IDisplayManager mIDisplayManager;
+ @Mock
private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Mock
private DevicePolicyManager mDevicePolicyManagerMock;
@@ -237,6 +247,8 @@
@Mock
private IVirtualDeviceSoundEffectListener mSoundEffectListener;
@Mock
+ private IVirtualDisplayCallback mVirtualDisplayCallback;
+ @Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@Mock
private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -271,9 +283,13 @@
private Intent createRestrictedActivityBlockedIntent(List displayCategories,
String targetDisplayCategory) {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
+ eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
+ VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
+ 420).setDisplayCategories(displayCategories).build();
+ mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback,
+ NONBLOCKED_APP_PACKAGE_NAME);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -327,6 +343,7 @@
mContext = Mockito.spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any());
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
mDevicePolicyManagerMock);
@@ -369,15 +386,13 @@
@Test
public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
- mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID_1);
-
- assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
+ assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
.isEqualTo(DEVICE_ID_DEFAULT);
}
@Test
public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
.isEqualTo(mDeviceImpl.getDeviceId());
@@ -503,10 +518,9 @@
@Test
public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
- GenericWindowPolicyController gwpc =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_2));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).isEmpty();
@@ -514,10 +528,9 @@
@Test
public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
- GenericWindowPolicyController gwpc =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_1));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -525,10 +538,10 @@
@Test
public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
- GenericWindowPolicyController gwpc =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_1, UID_2));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -538,11 +551,10 @@
public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
DEVICE_OWNER_UID_2);
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2);
- GenericWindowPolicyController gwpc =
- secondDevice.createWindowPolicyController(new ArrayList<>());
- secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+ secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+ Sets.newArraySet(UID_1));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
@@ -550,16 +562,16 @@
@Test
public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
DEVICE_OWNER_UID_2);
- GenericWindowPolicyController gwpc1 =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- GenericWindowPolicyController gwpc2 =
- secondDevice.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID_1);
- secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
- gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
- gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2);
+
+
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_1));
+ secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+ Sets.newArraySet(UID_1, UID_2));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(
@@ -568,8 +580,7 @@
@Test
public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
@@ -609,8 +620,8 @@
.setLanguageTag("fr-FR")
.build();
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
- secondDevice.mVirtualDisplayIds.add(DISPLAY_ID_2);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2);
mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
@@ -640,10 +651,9 @@
@Test
public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
// This call should not throw any exceptions.
- mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+ mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
}
@Test
@@ -659,8 +669,8 @@
@Test
public void onVirtualDisplayRemovedLocked_listenersNotified() {
mLocalService.registerVirtualDisplayListener(mDisplayListener);
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1);
TestableLooper.get(this).processAllMessages();
@@ -723,8 +733,7 @@
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), anyInt(), eq(null));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
@@ -733,12 +742,9 @@
@Test
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
- GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
- new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
assertThrows(IllegalStateException.class,
- () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1));
+ () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
@@ -749,13 +755,12 @@
public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
final int unknownDisplayId = 999;
assertThrows(IllegalStateException.class,
- () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
+ () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId));
}
@Test
public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -764,14 +769,13 @@
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
- mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+ mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
}
@Test
public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -825,7 +829,7 @@
@Test
public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
VirtualTouchscreenConfig positiveConfig =
new VirtualTouchscreenConfig.Builder(
/* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
@@ -863,7 +867,7 @@
@Test
public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
VirtualNavigationTouchpadConfig positiveConfig =
new VirtualNavigationTouchpadConfig.Builder(
/* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
@@ -888,7 +892,7 @@
@Test
public void createVirtualDpad_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
@@ -897,7 +901,7 @@
@Test
public void createVirtualKeyboard_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
@@ -906,7 +910,7 @@
@Test
public void createVirtualMouse_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
@@ -915,7 +919,7 @@
@Test
public void createVirtualTouchscreen_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
@@ -924,7 +928,7 @@
@Test
public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
@@ -934,7 +938,7 @@
@Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.onAudioSessionStarting(
@@ -951,7 +955,7 @@
@Test
public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
assertWithMessage("Virtual dpad should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -961,7 +965,7 @@
@Test
public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -971,7 +975,7 @@
@Test
public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -992,7 +996,7 @@
.setAssociatedDisplayId(DISPLAY_ID_1)
.build();
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -1005,7 +1009,7 @@
@Test
public void virtualDeviceWithoutKeyboard_noLocaleUpdate() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
// no preceding call to createVirtualKeyboard()
assertThat(mDeviceImpl.getDeviceLocaleList()).isNull();
@@ -1013,7 +1017,7 @@
@Test
public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
assertWithMessage("Virtual mouse should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1023,7 +1027,7 @@
@Test
public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1033,7 +1037,7 @@
@Test
public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
.that(
@@ -1055,8 +1059,7 @@
@Test
public void onAudioSessionStarting_hasVirtualAudioController() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
@@ -1065,8 +1068,7 @@
@Test
public void onAudioSessionEnded_noVirtualAudioController() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.onAudioSessionEnded();
@@ -1076,8 +1078,7 @@
@Test
public void close_cleanVirtualAudioController() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.close();
@@ -1321,9 +1322,9 @@
@Test
public void setShowPointerIcon_setsValueForAllDisplays() {
- mDeviceImpl.mVirtualDisplayIds.add(1);
- mDeviceImpl.mVirtualDisplayIds.add(2);
- mDeviceImpl.mVirtualDisplayIds.add(3);
+ addVirtualDisplay(mDeviceImpl, 1);
+ addVirtualDisplay(mDeviceImpl, 2);
+ addVirtualDisplay(mDeviceImpl, 3);
VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
.setAssociatedDisplayId(1)
.setInputDeviceName(DEVICE_NAME_1)
@@ -1346,7 +1347,9 @@
mDeviceImpl.createVirtualMouse(config1, BINDER);
mDeviceImpl.createVirtualMouse(config2, BINDER);
mDeviceImpl.createVirtualMouse(config3, BINDER);
+ clearInvocations(mInputManagerInternalMock);
mDeviceImpl.setShowPointerIcon(false);
+
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
mDeviceImpl.setShowPointerIcon(true);
@@ -1355,9 +1358,8 @@
@Test
public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1376,9 +1378,8 @@
@Test
public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1397,9 +1398,8 @@
@Test
public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1418,9 +1418,8 @@
@Test
public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1439,9 +1438,8 @@
@Test
public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1460,9 +1458,8 @@
@Test
public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1482,9 +1479,8 @@
@Test
public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
gwpc.onRunningAppsChanged(uids);
@@ -1497,11 +1493,10 @@
@Test
public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
- mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+ gwpc.unregisterRunningAppsChangedListener(mDeviceImpl);
// This call should not throw any exceptions.
gwpc.onRunningAppsChanged(uids);
@@ -1512,9 +1507,8 @@
@Test
public void canActivityBeLaunched_activityCanLaunch() {
Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -1537,9 +1531,8 @@
doReturn(interceptor).when(interceptor).asBinder();
doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -1581,9 +1574,8 @@
doReturn(interceptor).when(interceptor).asBinder();
doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -1626,8 +1618,7 @@
}
@Test
- public void
- restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
+ public void restrictedActivityNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1654,15 +1645,15 @@
@Test
public void getDisplayIdsForDevice_oneDisplay_resultContainsCorrectDisplayId() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
assertThat(displayIds).containsExactly(DISPLAY_ID_1);
}
@Test
public void getDisplayIdsForDevice_twoDisplays_resultContainsCorrectDisplayIds() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_2);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_2);
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2);
}
@@ -1677,15 +1668,22 @@
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
VirtualDeviceParams params) {
VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
- mInputController, mSensorController, mCameraAccessController,
- /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
+ mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
+ mInputController, mSensorController, mCameraAccessController
+ /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
- mRunningAppsChangedCallback, params);
+ mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
mVdms.addVirtualDevice(virtualDeviceImpl);
return virtualDeviceImpl;
}
+ private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
+ when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
+ eq(virtualDevice), any(), any())).thenReturn(displayId);
+ virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
+ NONBLOCKED_APP_PACKAGE_NAME);
+ }
+
/** Helper class to drop permissions temporarily and restore them at the end of a test. */
static final class DropShellPermissionsTemporarily implements AutoCloseable {
DropShellPermissionsTemporarily() {
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index fb1a8f8..c0a994b 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -40,6 +40,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
@@ -49,7 +52,8 @@
private static final int CONTEXT_HUB_ID = 3;
private static final String CONTEXT_HUB_STRING = "Context Hub Info Test";
- private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
@Mock private IContextHubWrapper mMockContextHubWrapper;
@Mock private ContextHubInfo mMockContextHubInfo;
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
@@ -62,20 +66,29 @@
when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING);
when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo);
- when(mMockContextHubWrapper.supportsLocationSettingNotifications())
- .thenReturn(true);
+ when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true);
when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true);
- when(mMockContextHubWrapper.supportsAirplaneModeSettingNotifications())
- .thenReturn(true);
- when(mMockContextHubWrapper.supportsMicrophoneSettingNotifications())
- .thenReturn(true);
+ when(mMockContextHubWrapper.supportsAirplaneModeSettingNotifications()).thenReturn(true);
+ when(mMockContextHubWrapper.supportsMicrophoneSettingNotifications()).thenReturn(true);
when(mMockContextHubWrapper.supportsBtSettingNotifications()).thenReturn(true);
}
-// TODO (b/254290317): These existing tests are to setup the testing infra for the ContextHub
-// service and verify the constructor correctly registers a context hub.
-// We need to augment these tests to cover the full behavior of the
-// ContextHub service
+ @Test
+ public void testDump_emptyPreloadedNanoappList() {
+ when(mMockContextHubWrapper.getPreloadedNanoappIds()).thenReturn(null);
+ StringWriter stringWriter = new StringWriter();
+
+ ContextHubService service = new ContextHubService(mContext, mMockContextHubWrapper);
+ service.dump(
+ new FileDescriptor(), new PrintWriter(stringWriter), /* args= */ new String[0]);
+
+ assertThat(stringWriter.toString()).isNotEmpty();
+ }
+
+ // TODO (b/254290317): These existing tests are to setup the testing infra for the ContextHub
+ // service and verify the constructor correctly registers a context hub.
+ // We need to augment these tests to cover the full behavior of the
+ // ContextHub service
@Test
public void testConstructorRegistersContextHub() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 5a0867f..daa6823 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -386,16 +386,16 @@
.when(mMockContext).enforceCallingPermission(anyString(), any());
assertThrows(SecurityException.class,
- () -> mTimeDetectorService.clearNetworkTime());
+ () -> mTimeDetectorService.clearLatestNetworkTime());
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
}
@Test
- public void testClearNetworkTime() throws Exception {
+ public void testClearLatestNetworkSuggestion() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
- mTimeDetectorService.clearNetworkTime();
+ mTimeDetectorService.clearLatestNetworkTime();
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
@@ -403,53 +403,48 @@
}
@Test
- public void testLatestNetworkTime() {
- NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
- 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
- when(mMockNtpTrustedTime.getCachedTimeResult())
- .thenReturn(latestNetworkTime);
- UnixEpochTime expected = new UnixEpochTime(
- latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
- assertEquals(expected, mTimeDetectorService.latestNetworkTime());
- }
-
- @Test
- public void testLatestNetworkTime_noTimeAvailable() {
- when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
- assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
- }
-
- @Test
public void testGetLatestNetworkSuggestion() {
- if (TimeDetectorNetworkTimeHelper.isInUse()) {
- NetworkTimeSuggestion latestNetworkTime = createNetworkTimeSuggestion();
- mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkTime);
+ NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
- assertEquals(latestNetworkTime, mTimeDetectorService.getLatestNetworkSuggestion());
+ assertEquals(latestNetworkSuggestion, mTimeDetectorService.getLatestNetworkSuggestion());
+ }
+
+ @Test
+ public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
+
+ assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ }
+
+ @Test
+ public void testLatestNetworkTime() {
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
+
+ assertEquals(latestNetworkSuggestion.getUnixEpochTime(),
+ mTimeDetectorService.latestNetworkTime());
} else {
NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
when(mMockNtpTrustedTime.getCachedTimeResult())
.thenReturn(latestNetworkTime);
- UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+ UnixEpochTime expected = new UnixEpochTime(
latestNetworkTime.getElapsedRealtimeMillis(),
latestNetworkTime.getTimeMillis());
- NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
- expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
- assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+ assertEquals(expected, mTimeDetectorService.latestNetworkTime());
}
}
@Test
- public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+ public void testLatestNetworkTime_noTimeAvailable() {
if (TimeDetectorNetworkTimeHelper.isInUse()) {
mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
-
- assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
} else {
when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
- assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
}
+ assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
}
@Test
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index b921838..4c0361d 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -263,7 +263,7 @@
+ instrumentation.getContext().getUserId() + " " + RoleManager.ROLE_HOME + " "
+ packageName + " 0");
waitUntil("Failed to get shortcut access",
- () -> hasShortcutAccess(instrumentation, packageName), 20);
+ () -> hasShortcutAccess(instrumentation, packageName), 60);
}
public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
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 ff5ede7..56461f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -17,8 +17,11 @@
package com.android.server.wm;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.window.BackNavigationInfo.typeToString;
@@ -42,8 +45,10 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
import android.window.BackMotionEvent;
@@ -320,6 +325,64 @@
assertThat(backNavigationInfo).isNull();
}
+ @Test
+ public void testTransitionHappensCancelNavigation() {
+ // Create a floating task and a fullscreen task, then navigating on fullscreen task.
+ // The navigation should not been cancelled when transition happens on floating task, and
+ // only be cancelled when transition happens on the navigating task.
+ final Task floatingTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
+ ACTIVITY_TYPE_STANDARD);
+ final ActivityRecord baseFloatingActivity = createActivityRecord(floatingTask);
+
+ final Task fullscreenTask = createTopTaskWithActivity();
+ withSystemCallback(fullscreenTask);
+ final ActivityRecord baseFullscreenActivity = fullscreenTask.getTopMostActivity();
+
+ final CountDownLatch navigationObserver = new CountDownLatch(1);
+ startBackNavigation(navigationObserver);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ final ActivityRecord secondFloatingActivity = createActivityRecord(floatingTask);
+ opening.add(secondFloatingActivity);
+ closing.add(baseFloatingActivity);
+ mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing);
+ assertEquals("Transition happen on an irrelevant task, callback should not been called",
+ 1, navigationObserver.getCount());
+
+ // Create a new activity above navigation target, the transition should cancel navigation.
+ final ActivityRecord topFullscreenActivity = createActivityRecord(fullscreenTask);
+ opening.clear();
+ closing.clear();
+ opening.add(topFullscreenActivity);
+ closing.add(baseFullscreenActivity);
+ mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing);
+ assertEquals("Transition happen on navigation task, callback should have been called",
+ 0, navigationObserver.getCount());
+ }
+
+ @Test
+ public void testWindowFocusChangeCancelNavigation() {
+ Task task = createTopTaskWithActivity();
+ withSystemCallback(task);
+ WindowState focusWindow = task.getTopVisibleAppMainWindow();
+ final CountDownLatch navigationObserver = new CountDownLatch(1);
+ startBackNavigation(navigationObserver);
+
+ mBackNavigationController.onFocusChanged(null);
+ assertEquals("change focus to null, callback should not have been called",
+ 1, navigationObserver.getCount());
+ mBackNavigationController.onFocusChanged(focusWindow);
+ assertEquals("change focus back, callback should not have been called",
+ 1, navigationObserver.getCount());
+
+ WindowState newWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlayWindow");
+ addToWindowMap(newWindow, true);
+ mBackNavigationController.onFocusChanged(newWindow);
+ assertEquals("Focus change, callback should have been called",
+ 0, navigationObserver.getCount());
+ }
+
private IOnBackInvokedCallback withSystemCallback(Task task) {
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
@@ -336,7 +399,14 @@
@Nullable
private BackNavigationInfo startBackNavigation() {
- return mBackNavigationController.startBackNavigation(null, mBackAnimationAdapter);
+ return mBackNavigationController.startBackNavigation(
+ createNavigationObserver(null), mBackAnimationAdapter);
+ }
+
+ @Nullable
+ private BackNavigationInfo startBackNavigation(CountDownLatch navigationObserverLatch) {
+ return mBackNavigationController.startBackNavigation(
+ createNavigationObserver(navigationObserverLatch), mBackAnimationAdapter);
}
@NonNull
@@ -371,6 +441,14 @@
};
}
+ private RemoteCallback createNavigationObserver(CountDownLatch latch) {
+ return new RemoteCallback(result -> {
+ if (latch != null) {
+ latch.countDown();
+ }
+ });
+ }
+
private Task initHomeActivity() {
final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
task.forAllLeafTasks((t) -> {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 4359234..025e1dc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -30,7 +30,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Disabled;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -41,7 +41,6 @@
import android.media.AudioManagerInternal;
import android.media.permission.Identity;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -89,8 +88,7 @@
static final boolean DEBUG = false;
/**
- * For apps targeting Android API Build.VERSION_CODES#UPSIDE_DOWN_CAKE and above,
- * implementors of the HotwordDetectionService must not augment the phrase IDs which are
+ * Implementors of the HotwordDetectionService must not augment the phrase IDs which are
* supplied via HotwordDetectionService
* #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
*
@@ -104,7 +102,7 @@
* </p>
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
public static final long ENFORCE_HOTWORD_PHRASE_ID = 215066299L;
private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 6b2bea0..97538c1 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -76,7 +76,10 @@
}
/**
- * Request Telecom set the call state to active.
+ * Request Telecom set the call state to active. This method should be called when either an
+ * outgoing call is ready to go active or a held call is ready to go active again. For incoming
+ * calls that are ready to be answered, use
+ * {@link CallControl#answer(int, Executor, OutcomeReceiver)}.
*
* @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
* will be called on.
@@ -106,6 +109,43 @@
}
/**
+ * Request Telecom answer an incoming call. For outgoing calls and calls that have been placed
+ * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}.
+ *
+ * @param videoState to report to Telecom. Telecom will store VideoState in the event another
+ * service/device requests it in order to continue the call on another screen.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback that will be completed on the Telecom side that details success or failure
+ * of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+ * switched the call state to active
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+ * the call state to active. A {@link CallException} will be passed
+ * that details why the operation failed.
+ */
+ public void answer(@android.telecom.CallAttributes.CallType int videoState,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ validateVideoState(videoState);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.answer(videoState, mCallId,
+ new CallControlResultReceiver("answer", executor, callback));
+
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
* Request Telecom set the call state to inactive. This the same as hold for two call endpoints
* but can be extended to setting a meeting to inactive.
*
@@ -343,4 +383,13 @@
}
}
+ /** @hide */
+ private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) {
+ if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) {
+ throw new IllegalArgumentException(TextUtils.formatSimple(
+ "The VideoState argument passed in, %d , is not a valid VideoState. The "
+ + "VideoState choices are limited to CallAttributes.AUDIO_CALL or"
+ + "CallAttributes.VIDEO_CALL", videoState));
+ }
+ }
}
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index 3e651e9..5e2c923 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -27,6 +27,7 @@
*/
oneway interface ICallControl {
void setActive(String callId, in ResultReceiver callback);
+ void answer(int videoState, String callId, in ResultReceiver callback);
void setInactive(String callId, in ResultReceiver callback);
void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
void startCallStreaming(String callId, in ResultReceiver callback);
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 7eccd1a..728dfa6 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.service.carrier.CarrierIdentifier;
+import android.telephony.SubscriptionInfo;
import android.telephony.UiccAccessRule;
import android.text.TextUtils;
@@ -451,6 +452,8 @@
+ mPolicyRules
+ ", accessRules="
+ Arrays.toString(mAccessRules)
+ + ", iccid="
+ + SubscriptionInfo.givePrintableIccid(mIccid)
+ ")";
}
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index b418a02..3e2c7c4 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4401,7 +4401,6 @@
*
* @throws IllegalArgumentException if subscription is invalid.
* @throws SecurityException if the caller doesn't have permissions required.
- * @throws IllegalStateException if subscription service is not available.
*
* @hide
*/
@@ -4418,8 +4417,7 @@
if (iSub != null) {
return iSub.getSubscriptionUserHandle(subscriptionId);
} else {
- throw new IllegalStateException("[getSubscriptionUserHandle]: "
- + "subscription service unavailable");
+ Log.e(LOG_TAG, "[getSubscriptionUserHandle]: subscription service unavailable");
}
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 174675f..a8fb36b 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -342,8 +342,8 @@
/**
* @return The SIM slot index associated with this ImsFeature.
*
- * @see SubscriptionManager#getSubscriptionIds(int) for more information on getting the
- * subscription IDs associated with this slot.
+ * @see SubscriptionManager#getSubscriptionId(int) for more information on getting the
+ * subscription ID associated with this slot.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteService.java b/telephony/java/android/telephony/satellite/stub/SatelliteService.java
index 3861c5b..5b96e34 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteService.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteService.java
@@ -20,7 +20,8 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
-import android.util.Log;
+
+import com.android.telephony.Rlog;
/**
* Main SatelliteService implementation, which binds via the Telephony SatelliteServiceController.
@@ -44,7 +45,7 @@
* @hide
*/
public class SatelliteService extends Service {
- public static final String TAG = "SatelliteService";
+ private static final String TAG = "SatelliteService";
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.telephony.satellite.SatelliteService";
@@ -55,7 +56,7 @@
@Override
public IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- Log.e(TAG, "SatelliteService bound");
+ Rlog.d(TAG, "SatelliteService bound");
return new SatelliteImplBase(Runnable::run).getBinder();
}
return null;