Merge "Add description of regional preference" into udc-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index 9f3ceb3..2161186 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19370,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);
@@ -42066,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/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/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/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/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/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/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/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/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 e3236b3..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 ->
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/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/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/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/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 8b913db..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;
@@ -631,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/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/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/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);