summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java2
-rw-r--r--core/api/current.txt2
-rw-r--r--core/java/android/os/Trace.java6
-rw-r--r--core/java/android/os/incremental/IncrementalFileStorages.java10
-rw-r--r--core/java/android/speech/IRecognitionService.aidl5
-rw-r--r--core/java/android/speech/IRecognitionServiceManager.aidl10
-rw-r--r--core/java/android/speech/IRecognitionServiceManagerCallback.aidl2
-rw-r--r--core/java/android/speech/RecognitionService.java18
-rw-r--r--core/java/android/speech/SpeechRecognizer.java243
-rw-r--r--core/java/android/uwb/OWNERS1
-rw-r--r--core/java/android/uwb/TEST_MAPPING5
-rw-r--r--core/jni/android_os_Trace.cpp2
-rw-r--r--core/tests/uwbtests/src/android/uwb/AngleMeasurementTest.java85
-rw-r--r--core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java89
-rw-r--r--core/tests/uwbtests/src/android/uwb/DistanceMeasurementTest.java87
-rw-r--r--core/tests/uwbtests/src/android/uwb/RangingMeasurementTest.java95
-rw-r--r--core/tests/uwbtests/src/android/uwb/RangingReportTest.java90
-rw-r--r--core/tests/uwbtests/src/android/uwb/RangingSessionTest.java378
-rw-r--r--core/tests/uwbtests/src/android/uwb/SessionHandleTest.java52
-rw-r--r--core/tests/uwbtests/src/android/uwb/UwbAddressTest.java79
-rw-r--r--core/tests/uwbtests/src/android/uwb/UwbManagerTest.java49
-rw-r--r--media/java/android/media/MediaDrm.java81
-rw-r--r--media/java/android/media/tv/TvInputManager.java8
-rw-r--r--media/jni/android_media_MediaDrm.cpp2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java13
-rw-r--r--services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java343
-rw-r--r--services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java11
-rw-r--r--services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java204
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java17
-rw-r--r--tests/FlickerTests/OWNERS1
31 files changed, 786 insertions, 1224 deletions
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java
index 8d2ac0276592..330a19e03c39 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java
@@ -89,7 +89,7 @@ public class WindowPerfRunPreconditionBase extends RunListener {
navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
break;
}
- executeShellCommand("cmd overlay enable-exclusive " + navOverlay);
+ executeShellCommand("cmd overlay enable-exclusive --category " + navOverlay);
});
/** It only executes once before all tests. */
diff --git a/core/api/current.txt b/core/api/current.txt
index d3d754c0d8a5..a21b6938c189 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -39026,7 +39026,9 @@ package android.speech {
field public static final int ERROR_NO_MATCH = 7; // 0x7
field public static final int ERROR_RECOGNIZER_BUSY = 8; // 0x8
field public static final int ERROR_SERVER = 4; // 0x4
+ field public static final int ERROR_SERVER_DISCONNECTED = 11; // 0xb
field public static final int ERROR_SPEECH_TIMEOUT = 6; // 0x6
+ field public static final int ERROR_TOO_MANY_REQUESTS = 10; // 0xa
field public static final String RESULTS_RECOGNITION = "results_recognition";
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 9c9e4995d673..c8cbc517b226 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -168,8 +168,10 @@ public final class Trace {
}
/**
- * Set whether application tracing is allowed for this process. This is intended to be set
- * once at application start-up time based on whether the application is debuggable.
+ * From Android S, this is no-op.
+ *
+ * Before, set whether application tracing is allowed for this process. This is intended to be
+ * set once at application start-up time based on whether the application is debuggable.
*
* @hide
*/
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index f2fe71913bb1..a078e0434867 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -36,6 +36,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.DataLoaderParams;
import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.IPackageLoadingProgressCallback;
import android.content.pm.InstallationFileParcel;
import java.io.File;
@@ -71,7 +72,8 @@ public final class IncrementalFileStorages {
@Nullable StorageHealthCheckParams healthCheckParams,
@Nullable IStorageHealthListener healthListener,
@NonNull List<InstallationFileParcel> addedFiles,
- @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
+ @NonNull PerUidReadTimeouts[] perUidReadTimeouts,
+ IPackageLoadingProgressCallback progressCallback) throws IOException {
// TODO(b/136132412): validity check if session should not be incremental
IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
Context.INCREMENTAL_SERVICE);
@@ -95,6 +97,11 @@ public final class IncrementalFileStorages {
throw new IOException("Unknown file location: " + file.location);
}
}
+ // Register progress loading callback after files have been added
+ if (progressCallback != null) {
+ incrementalManager.registerLoadingProgressCallback(stageDir.getAbsolutePath(),
+ progressCallback);
+ }
result.startLoading(dataLoaderParams, statusListener, healthCheckParams, healthListener,
perUidReadTimeouts);
@@ -205,6 +212,7 @@ public final class IncrementalFileStorages {
try {
mDefaultStorage.unBind(mStageDir.getAbsolutePath());
+ mDefaultStorage.unregisterLoadingProgressListener();
} catch (IOException ignored) {
}
mDefaultStorage = null;
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index f91e122ea9cb..cc1cdedd0f96 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -43,7 +43,7 @@ oneway interface IRecognitionService {
* @param featureId The feature in the package
*/
void startListening(in Intent recognizerIntent, in IRecognitionListener listener,
- String packageName, String featureId);
+ String packageName, String featureId, int callingUid);
/**
* Stops listening for speech. Speech captured so far will be recognized as
@@ -62,6 +62,7 @@ oneway interface IRecognitionService {
* @param listener to receive callbacks, note that this must be non-null
* @param packageName the package name calling this API
* @param featureId The feature in the package
+ * @param isShutdown Whether the cancellation is caused by a client calling #shutdown
*/
- void cancel(in IRecognitionListener listener, String packageName, String featureId);
+ void cancel(in IRecognitionListener listener, String packageName, String featureId, boolean isShutdown);
}
diff --git a/core/java/android/speech/IRecognitionServiceManager.aidl b/core/java/android/speech/IRecognitionServiceManager.aidl
index 7158ba2f9f63..8e5292d1ddf1 100644
--- a/core/java/android/speech/IRecognitionServiceManager.aidl
+++ b/core/java/android/speech/IRecognitionServiceManager.aidl
@@ -16,6 +16,8 @@
package android.speech;
+import android.content.ComponentName;
+
import android.speech.IRecognitionServiceManagerCallback;
/**
@@ -23,6 +25,10 @@ import android.speech.IRecognitionServiceManagerCallback;
*
* {@hide}
*/
-interface IRecognitionServiceManager {
- void createSession(in IRecognitionServiceManagerCallback callback);
+oneway interface IRecognitionServiceManager {
+ void createSession(
+ in ComponentName componentName,
+ in IBinder clientToken,
+ boolean onDevice,
+ in IRecognitionServiceManagerCallback callback);
}
diff --git a/core/java/android/speech/IRecognitionServiceManagerCallback.aidl b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
index d760810deda8..26afdaafd919 100644
--- a/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
+++ b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
@@ -25,5 +25,5 @@ import android.speech.IRecognitionService;
*/
oneway interface IRecognitionServiceManagerCallback {
void onSuccess(in IRecognitionService service);
- void onError();
+ void onError(int errorCode);
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index c97dbfe38ead..fd584f191743 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -105,17 +105,6 @@ public abstract class RecognitionService extends Service {
int callingUid) {
if (mCurrentCallback == null) {
if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
- try {
- listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_CANCEL, listener));
- }
- }, 0);
- } catch (RemoteException re) {
- Log.e(TAG, "dead listener on startListening");
- return;
- }
mCurrentCallback = new Callback(listener, callingUid);
RecognitionService.this.onStartListening(intent, mCurrentCallback);
} else {
@@ -352,7 +341,6 @@ public abstract class RecognitionService extends Service {
* Return the Linux uid assigned to the process that sent you the current transaction that
* is being processed. This is obtained from {@link Binder#getCallingUid()}.
*/
- // TODO(b/176578753): need to make sure this is fixed when proxied through system.
public int getCallingUid() {
return mCallingUid;
}
@@ -368,7 +356,7 @@ public abstract class RecognitionService extends Service {
@Override
public void startListening(Intent recognizerIntent, IRecognitionListener listener,
- String packageName, String featureId) {
+ String packageName, String featureId, int callingUid) {
Preconditions.checkNotNull(packageName);
if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
@@ -377,7 +365,7 @@ public abstract class RecognitionService extends Service {
packageName, featureId)) {
service.mHandler.sendMessage(Message.obtain(service.mHandler,
MSG_START_LISTENING, service.new StartListeningArgs(
- recognizerIntent, listener, Binder.getCallingUid())));
+ recognizerIntent, listener, callingUid)));
}
}
@@ -397,7 +385,7 @@ public abstract class RecognitionService extends Service {
@Override
public void cancel(IRecognitionListener listener, String packageName,
- String featureId) {
+ String featureId, boolean isShutdown) {
Preconditions.checkNotNull(packageName);
if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index de879c63a1a6..850f997a2d2f 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -20,8 +20,8 @@ import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -32,10 +32,9 @@ import android.os.ServiceManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Queue;
/**
* This class provides access to the speech recognition service. This service allows access to the
@@ -107,6 +106,12 @@ public class SpeechRecognizer {
/** Insufficient permissions */
public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
+ /** Too many requests from the same client. */
+ public static final int ERROR_TOO_MANY_REQUESTS = 10;
+
+ /** Server has been disconnected, e.g. because the app has crashed. */
+ public static final int ERROR_SERVER_DISCONNECTED = 11;
+
/** action codes */
private final static int MSG_START = 1;
private final static int MSG_STOP = 2;
@@ -116,9 +121,6 @@ public class SpeechRecognizer {
/** The actual RecognitionService endpoint */
private IRecognitionService mService;
- /** The connection to the actual service */
- private Connection mConnection;
-
/** Context with which the manager was created */
private final Context mContext;
@@ -151,15 +153,11 @@ public class SpeechRecognizer {
}
};
- /**
- * Temporary queue, saving the messages until the connection will be established, afterwards,
- * only mHandler will receive the messages
- */
- private final Queue<Message> mPendingTasks = new LinkedList<Message>();
-
/** The Listener that will receive all the callbacks */
private final InternalListener mListener = new InternalListener();
+ private final IBinder mClientToken = new Binder();
+
/**
* The right way to create a {@code SpeechRecognizer} is by using
* {@link #createSpeechRecognizer} static factory method
@@ -181,30 +179,6 @@ public class SpeechRecognizer {
}
/**
- * Basic ServiceConnection that records the mService variable. Additionally, on creation it
- * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
- */
- private class Connection implements ServiceConnection {
-
- public void onServiceConnected(final ComponentName name, final IBinder service) {
- // always done on the application main thread, so no need to send message to mHandler
- mService = IRecognitionService.Stub.asInterface(service);
- if (DBG) Log.d(TAG, "onServiceConnected - Success");
- while (!mPendingTasks.isEmpty()) {
- mHandler.sendMessage(mPendingTasks.poll());
- }
- }
-
- public void onServiceDisconnected(final ComponentName name) {
- // always done on the application main thread, so no need to send message to mHandler
- mService = null;
- mConnection = null;
- mPendingTasks.clear();
- if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
- }
- }
-
- /**
* Checks whether a speech recognition service is available on the system. If this method
* returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
* fail.
@@ -303,87 +277,52 @@ public class SpeechRecognizer {
throw new IllegalArgumentException("intent must not be null");
}
checkIsCalledFromMainThread();
- if (mConnection == null) { // first time connection
- // TODO(b/176578753): both flows should go through system service.
- if (mOnDevice) {
- connectToSystemService();
- } else {
- connectToService();
- }
- }
- putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
- }
- private void connectToSystemService() {
- mManagerService = IRecognitionServiceManager.Stub.asInterface(
- ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
-
- if (mManagerService == null) {
- mListener.onError(ERROR_CLIENT);
- return;
- }
-
- try {
- // TODO(b/176578753): this has to supply information on whether to use on-device impl.
- mManagerService.createSession(new IRecognitionServiceManagerCallback.Stub(){
- @Override
- public void onSuccess(IRecognitionService service) throws RemoteException {
- mService = service;
- }
-
- @Override
- public void onError() throws RemoteException {
- Log.e(TAG, "Bind to system recognition service failed");
- mListener.onError(ERROR_CLIENT);
- }
- });
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
- private void connectToService() {
- mConnection = new Connection();
-
- Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
-
- if (mServiceComponent == null) {
- String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.VOICE_RECOGNITION_SERVICE);
-
- if (TextUtils.isEmpty(serviceComponent)) {
- Log.e(TAG, "no selected voice recognition service");
- mListener.onError(ERROR_CLIENT);
- return;
+ if (DBG) {
+ Slog.i(TAG, "#startListening called");
+ if (mService == null) {
+ Slog.i(TAG, "Connection is not established yet");
}
+ }
- serviceIntent.setComponent(
- ComponentName.unflattenFromString(serviceComponent));
+ if (mService == null) {
+ // First time connection: first establish a connection, then dispatch #startListening.
+ connectToSystemService(
+ () -> putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)));
} else {
- serviceIntent.setComponent(mServiceComponent);
- }
- if (!mContext.bindService(serviceIntent, mConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
- Log.e(TAG, "bind to recognition service failed");
- mConnection = null;
- mService = null;
- mListener.onError(ERROR_CLIENT);
- return;
+ putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
}
}
/**
* Stops listening for speech. Speech captured so far will be recognized as if the user had
- * stopped speaking at this point. Note that in the default case, this does not need to be
- * called, as the speech endpointer will automatically stop the recognizer listening when it
- * determines speech has completed. However, you can manipulate endpointer parameters directly
- * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
- * want to manually call this method to stop listening sooner. Please note that
+ * stopped speaking at this point.
+ *
+ * <p>Note that in the default case, this does not need to be called, as the speech endpointer
+ * will automatically stop the recognizer listening when it determines speech has completed.
+ * However, you can manipulate endpointer parameters directly using the intent extras defined in
+ * {@link RecognizerIntent}, in which case you may sometimes want to manually call this method
+ * to stop listening sooner.
+ *
+ * <p>Upon invocation clients must wait until {@link RecognitionListener#onResults} or
+ * {@link RecognitionListener#onError} are invoked before calling
+ * {@link SpeechRecognizer#startListening} again. Otherwise such an attempt would be rejected by
+ * recognition service.
+ *
+ * <p>Please note that
* {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
* no notifications will be received.
*/
public void stopListening() {
checkIsCalledFromMainThread();
+
+ if (DBG) {
+ Slog.i(TAG, "#stopListening called");
+ if (mService == null) {
+ Slog.i(TAG, "Connection is not established yet");
+ }
+ }
+
putMessage(Message.obtain(mHandler, MSG_STOP));
}
@@ -405,11 +344,7 @@ public class SpeechRecognizer {
}
private void putMessage(Message msg) {
- if (mService == null) {
- mPendingTasks.offer(msg);
- } else {
- mHandler.sendMessage(msg);
- }
+ mHandler.sendMessage(msg);
}
/** sends the actual message to the service */
@@ -419,7 +354,7 @@ public class SpeechRecognizer {
}
try {
mService.startListening(recognizerIntent, mListener, mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), android.os.Process.myUid());
if (DBG) Log.d(TAG, "service start listening command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "startListening() failed", e);
@@ -448,7 +383,11 @@ public class SpeechRecognizer {
return;
}
try {
- mService.cancel(mListener, mContext.getOpPackageName(), mContext.getAttributionTag());
+ mService.cancel(
+ mListener,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ false /* isShutdown */);
if (DBG) Log.d(TAG, "service cancel command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "cancel() failed", e);
@@ -471,28 +410,94 @@ public class SpeechRecognizer {
mListener.mInternalListener = listener;
}
- /**
- * Destroys the {@code SpeechRecognizer} object.
- */
+ /** Destroys the {@code SpeechRecognizer} object. */
public void destroy() {
if (mService != null) {
try {
mService.cancel(mListener, mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), true /* isShutdown */);
} catch (final RemoteException e) {
// Not important
}
}
- if (mConnection != null) {
- mContext.unbindService(mConnection);
- }
- mPendingTasks.clear();
mService = null;
- mConnection = null;
mListener.mInternalListener = null;
}
+ /** Establishes a connection to system server proxy and initializes the session. */
+ private void connectToSystemService(Runnable onSuccess) {
+ mManagerService = IRecognitionServiceManager.Stub.asInterface(
+ ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
+
+ if (mManagerService == null) {
+ mListener.onError(ERROR_CLIENT);
+ return;
+ }
+
+ ComponentName componentName = getSpeechRecognizerComponentName();
+
+ if (!mOnDevice && componentName == null) {
+ mListener.onError(ERROR_CLIENT);
+ return;
+ }
+
+ try {
+ mManagerService.createSession(
+ componentName,
+ mClientToken,
+ mOnDevice,
+ new IRecognitionServiceManagerCallback.Stub(){
+ @Override
+ public void onSuccess(IRecognitionService service) throws RemoteException {
+ mService = service;
+ onSuccess.run();
+ }
+
+ @Override
+ public void onError(int errorCode) throws RemoteException {
+ Log.e(TAG, "Bind to system recognition service failed");
+ mListener.onError(errorCode);
+ }
+ });
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the component name to be used for establishing a connection, based on the parameters
+ * used during initialization.
+ *
+ * <p>Note the 3 different scenarios:
+ * <ol>
+ * <li>On-device speech recognizer which is determined by the manufacturer and not
+ * changeable by the user
+ * <li>Default user-selected speech recognizer as specified by
+ * {@code Settings.Secure.VOICE_RECOGNITION_SERVICE}
+ * <li>Custom speech recognizer supplied by the client.
+ */
+ private ComponentName getSpeechRecognizerComponentName() {
+ if (mOnDevice) {
+ return null;
+ }
+
+ if (mServiceComponent != null) {
+ return mServiceComponent;
+ }
+
+ String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE);
+
+ if (TextUtils.isEmpty(serviceComponent)) {
+ Log.e(TAG, "no selected voice recognition service");
+ mListener.onError(ERROR_CLIENT);
+ return null;
+ }
+
+ return ComponentName.unflattenFromString(serviceComponent);
+ }
+
/**
* Internal wrapper of IRecognitionListener which will propagate the results to
* RecognitionListener
diff --git a/core/java/android/uwb/OWNERS b/core/java/android/uwb/OWNERS
index ea41c3984dfd..17936ae7bb73 100644
--- a/core/java/android/uwb/OWNERS
+++ b/core/java/android/uwb/OWNERS
@@ -1,5 +1,6 @@
bstack@google.com
eliptus@google.com
jsolnit@google.com
+matbev@google.com
siyuanh@google.com
zachoverflow@google.com
diff --git a/core/java/android/uwb/TEST_MAPPING b/core/java/android/uwb/TEST_MAPPING
index 9e50bd64d089..08ed2c7b71d9 100644
--- a/core/java/android/uwb/TEST_MAPPING
+++ b/core/java/android/uwb/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "UwbManagerTests"
+ },
+ {
+ "name": "CtsUwbTestCases"
}
]
-} \ No newline at end of file
+}
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index 0f7611a8ead1..f67007cda209 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -83,7 +83,7 @@ static void android_os_Trace_nativeAsyncTraceEnd(JNIEnv* env, jclass,
}
static void android_os_Trace_nativeSetAppTracingAllowed(JNIEnv*, jclass, jboolean allowed) {
- atrace_set_debuggable(allowed);
+ atrace_update_tags();
}
static void android_os_Trace_nativeSetTracingEnabled(JNIEnv*, jclass, jboolean enabled) {
diff --git a/core/tests/uwbtests/src/android/uwb/AngleMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/AngleMeasurementTest.java
deleted file mode 100644
index 7769c28202f2..000000000000
--- a/core/tests/uwbtests/src/android/uwb/AngleMeasurementTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link AngleMeasurement}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AngleMeasurementTest {
- private static final double EPSILON = 0.00000000001;
-
- @Test
- public void testBuilder() {
- double radians = 0.1234;
- double errorRadians = 0.5678;
- double confidence = 0.5;
-
- AngleMeasurement.Builder builder = new AngleMeasurement.Builder();
- tryBuild(builder, false);
-
- builder.setRadians(radians);
- tryBuild(builder, false);
-
- builder.setErrorRadians(errorRadians);
- tryBuild(builder, false);
-
- builder.setConfidenceLevel(confidence);
- AngleMeasurement measurement = tryBuild(builder, true);
-
- assertEquals(measurement.getRadians(), radians, 0);
- assertEquals(measurement.getErrorRadians(), errorRadians, 0);
- assertEquals(measurement.getConfidenceLevel(), confidence, 0);
- }
-
- private AngleMeasurement tryBuild(AngleMeasurement.Builder builder, boolean expectSuccess) {
- AngleMeasurement measurement = null;
- try {
- measurement = builder.build();
- if (!expectSuccess) {
- fail("Expected AngleMeasurement.Builder.build() to fail, but it succeeded");
- }
- } catch (IllegalStateException e) {
- if (expectSuccess) {
- fail("Expected AngleMeasurement.Builder.build() to succeed, but it failed");
- }
- }
- return measurement;
- }
-
- @Test
- public void testParcel() {
- Parcel parcel = Parcel.obtain();
- AngleMeasurement measurement = UwbTestUtils.getAngleMeasurement();
- measurement.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- AngleMeasurement fromParcel = AngleMeasurement.CREATOR.createFromParcel(parcel);
- assertEquals(measurement, fromParcel);
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java
deleted file mode 100644
index 9394dec7f46f..000000000000
--- a/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link AngleOfArrivalMeasurement}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AngleOfArrivalMeasurementTest {
-
- @Test
- public void testBuilder() {
- AngleMeasurement azimuth = UwbTestUtils.getAngleMeasurement();
- AngleMeasurement altitude = UwbTestUtils.getAngleMeasurement();
-
- AngleOfArrivalMeasurement.Builder builder = new AngleOfArrivalMeasurement.Builder();
- tryBuild(builder, false);
-
- builder.setAltitude(altitude);
- tryBuild(builder, false);
-
- builder.setAzimuth(azimuth);
- AngleOfArrivalMeasurement measurement = tryBuild(builder, true);
-
- assertEquals(azimuth, measurement.getAzimuth());
- assertEquals(altitude, measurement.getAltitude());
- }
-
- private AngleMeasurement getAngleMeasurement(double radian, double error, double confidence) {
- return new AngleMeasurement.Builder()
- .setRadians(radian)
- .setErrorRadians(error)
- .setConfidenceLevel(confidence)
- .build();
- }
-
- private AngleOfArrivalMeasurement tryBuild(AngleOfArrivalMeasurement.Builder builder,
- boolean expectSuccess) {
- AngleOfArrivalMeasurement measurement = null;
- try {
- measurement = builder.build();
- if (!expectSuccess) {
- fail("Expected AngleOfArrivalMeasurement.Builder.build() to fail");
- }
- } catch (IllegalStateException e) {
- if (expectSuccess) {
- fail("Expected AngleOfArrivalMeasurement.Builder.build() to succeed");
- }
- }
- return measurement;
- }
-
- @Test
- public void testParcel() {
- Parcel parcel = Parcel.obtain();
- AngleOfArrivalMeasurement measurement = UwbTestUtils.getAngleOfArrivalMeasurement();
- measurement.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- AngleOfArrivalMeasurement fromParcel =
- AngleOfArrivalMeasurement.CREATOR.createFromParcel(parcel);
- assertEquals(measurement, fromParcel);
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/DistanceMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/DistanceMeasurementTest.java
deleted file mode 100644
index 439c884723be..000000000000
--- a/core/tests/uwbtests/src/android/uwb/DistanceMeasurementTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link DistanceMeasurement}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DistanceMeasurementTest {
- private static final double EPSILON = 0.00000000001;
-
- @Test
- public void testBuilder() {
- double meters = 0.12;
- double error = 0.54;
- double confidence = 0.99;
-
- DistanceMeasurement.Builder builder = new DistanceMeasurement.Builder();
- tryBuild(builder, false);
-
- builder.setMeters(meters);
- tryBuild(builder, false);
-
- builder.setErrorMeters(error);
- tryBuild(builder, false);
-
- builder.setConfidenceLevel(confidence);
- DistanceMeasurement measurement = tryBuild(builder, true);
-
- assertEquals(meters, measurement.getMeters(), 0);
- assertEquals(error, measurement.getErrorMeters(), 0);
- assertEquals(confidence, measurement.getConfidenceLevel(), 0);
- }
-
- private DistanceMeasurement tryBuild(DistanceMeasurement.Builder builder,
- boolean expectSuccess) {
- DistanceMeasurement measurement = null;
- try {
- measurement = builder.build();
- if (!expectSuccess) {
- fail("Expected DistanceMeasurement.Builder.build() to fail");
- }
- } catch (IllegalStateException e) {
- if (expectSuccess) {
- fail("Expected DistanceMeasurement.Builder.build() to succeed");
- }
- }
- return measurement;
- }
-
- @Test
- public void testParcel() {
- Parcel parcel = Parcel.obtain();
- DistanceMeasurement measurement = UwbTestUtils.getDistanceMeasurement();
- measurement.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- DistanceMeasurement fromParcel =
- DistanceMeasurement.CREATOR.createFromParcel(parcel);
- assertEquals(measurement, fromParcel);
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/RangingMeasurementTest.java
deleted file mode 100644
index edd4d08992ba..000000000000
--- a/core/tests/uwbtests/src/android/uwb/RangingMeasurementTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.os.Parcel;
-import android.os.SystemClock;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link RangingMeasurement}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RangingMeasurementTest {
-
- @Test
- public void testBuilder() {
- int status = RangingMeasurement.RANGING_STATUS_SUCCESS;
- UwbAddress address = UwbTestUtils.getUwbAddress(false);
- long time = SystemClock.elapsedRealtimeNanos();
- AngleOfArrivalMeasurement angleMeasurement = UwbTestUtils.getAngleOfArrivalMeasurement();
- DistanceMeasurement distanceMeasurement = UwbTestUtils.getDistanceMeasurement();
-
- RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
-
- builder.setStatus(status);
- tryBuild(builder, false);
-
- builder.setElapsedRealtimeNanos(time);
- tryBuild(builder, false);
-
- builder.setAngleOfArrivalMeasurement(angleMeasurement);
- tryBuild(builder, false);
-
- builder.setDistanceMeasurement(distanceMeasurement);
- tryBuild(builder, false);
-
- builder.setRemoteDeviceAddress(address);
- RangingMeasurement measurement = tryBuild(builder, true);
-
- assertEquals(status, measurement.getStatus());
- assertEquals(address, measurement.getRemoteDeviceAddress());
- assertEquals(time, measurement.getElapsedRealtimeNanos());
- assertEquals(angleMeasurement, measurement.getAngleOfArrivalMeasurement());
- assertEquals(distanceMeasurement, measurement.getDistanceMeasurement());
- }
-
- private RangingMeasurement tryBuild(RangingMeasurement.Builder builder,
- boolean expectSuccess) {
- RangingMeasurement measurement = null;
- try {
- measurement = builder.build();
- if (!expectSuccess) {
- fail("Expected RangingMeasurement.Builder.build() to fail");
- }
- } catch (IllegalStateException e) {
- if (expectSuccess) {
- fail("Expected DistanceMeasurement.Builder.build() to succeed");
- }
- }
- return measurement;
- }
-
- @Test
- public void testParcel() {
- Parcel parcel = Parcel.obtain();
- RangingMeasurement measurement = UwbTestUtils.getRangingMeasurement();
- measurement.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- RangingMeasurement fromParcel = RangingMeasurement.CREATOR.createFromParcel(parcel);
- assertEquals(measurement, fromParcel);
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingReportTest.java b/core/tests/uwbtests/src/android/uwb/RangingReportTest.java
deleted file mode 100644
index 64c48ba4b6f4..000000000000
--- a/core/tests/uwbtests/src/android/uwb/RangingReportTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-/**
- * Test of {@link RangingReport}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RangingReportTest {
-
- @Test
- public void testBuilder() {
- List<RangingMeasurement> measurements = UwbTestUtils.getRangingMeasurements(5);
-
- RangingReport.Builder builder = new RangingReport.Builder();
- builder.addMeasurements(measurements);
- RangingReport report = tryBuild(builder, true);
- verifyMeasurementsEqual(measurements, report.getMeasurements());
-
-
- builder = new RangingReport.Builder();
- for (RangingMeasurement measurement : measurements) {
- builder.addMeasurement(measurement);
- }
- report = tryBuild(builder, true);
- verifyMeasurementsEqual(measurements, report.getMeasurements());
- }
-
- private void verifyMeasurementsEqual(List<RangingMeasurement> expected,
- List<RangingMeasurement> actual) {
- assertEquals(expected.size(), actual.size());
- for (int i = 0; i < expected.size(); i++) {
- assertEquals(expected.get(i), actual.get(i));
- }
- }
-
- private RangingReport tryBuild(RangingReport.Builder builder,
- boolean expectSuccess) {
- RangingReport report = null;
- try {
- report = builder.build();
- if (!expectSuccess) {
- fail("Expected RangingReport.Builder.build() to fail");
- }
- } catch (IllegalStateException e) {
- if (expectSuccess) {
- fail("Expected RangingReport.Builder.build() to succeed");
- }
- }
- return report;
- }
-
- @Test
- public void testParcel() {
- Parcel parcel = Parcel.obtain();
- RangingReport report = UwbTestUtils.getRangingReports(5);
- report.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- RangingReport fromParcel = RangingReport.CREATOR.createFromParcel(parcel);
- assertEquals(report, fromParcel);
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java
deleted file mode 100644
index e5eea26f5d11..000000000000
--- a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.concurrent.Executor;
-
-/**
- * Test of {@link RangingSession}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RangingSessionTest {
- private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
- private static final PersistableBundle PARAMS = new PersistableBundle();
- private static final @RangingSession.Callback.Reason int REASON =
- RangingSession.Callback.REASON_GENERIC_ERROR;
-
- @Test
- public void testOnRangingOpened_OnOpenSuccessCalled() {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- verifyOpenState(session, false);
-
- session.onRangingOpened();
- verifyOpenState(session, true);
-
- // Verify that the onOpenSuccess callback was invoked
- verify(callback, times(1)).onOpened(eq(session));
- verify(callback, times(0)).onClosed(anyInt(), any());
- }
-
- @Test
- public void testOnRangingOpened_CannotOpenClosedSession() {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-
- session.onRangingOpened();
- verifyOpenState(session, true);
- verify(callback, times(1)).onOpened(eq(session));
- verify(callback, times(0)).onClosed(anyInt(), any());
-
- session.onRangingClosed(REASON, PARAMS);
- verifyOpenState(session, false);
- verify(callback, times(1)).onOpened(eq(session));
- verify(callback, times(1)).onClosed(anyInt(), any());
-
- // Now invoke the ranging started callback and ensure the session remains closed
- session.onRangingOpened();
- verifyOpenState(session, false);
- verify(callback, times(1)).onOpened(eq(session));
- verify(callback, times(1)).onClosed(anyInt(), any());
- }
-
- @Test
- public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- verifyOpenState(session, false);
-
- session.onRangingClosed(REASON, PARAMS);
- verifyOpenState(session, false);
-
- // Verify that the onOpenSuccess callback was invoked
- verify(callback, times(0)).onOpened(eq(session));
- verify(callback, times(1)).onClosed(anyInt(), any());
- }
-
- @Test
- public void testOnRangingClosed_OnClosedCalled() {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- session.onRangingStarted(PARAMS);
- session.onRangingClosed(REASON, PARAMS);
- verify(callback, times(1)).onClosed(anyInt(), any());
-
- verifyOpenState(session, false);
- session.onRangingClosed(REASON, PARAMS);
- verify(callback, times(2)).onClosed(anyInt(), any());
- }
-
- @Test
- public void testOnRangingResult_OnReportReceivedCalled() {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- verifyOpenState(session, false);
-
- session.onRangingStarted(PARAMS);
- verifyOpenState(session, true);
-
- RangingReport report = UwbTestUtils.getRangingReports(1);
- session.onRangingResult(report);
- verify(callback, times(1)).onReportReceived(eq(report));
- }
-
- @Test
- public void testStart_CannotStartIfAlreadyStarted() throws RemoteException {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
- session.onRangingOpened();
-
- session.start(PARAMS);
- verify(callback, times(1)).onStarted(any());
-
- // Calling start again should throw an illegal state
- verifyThrowIllegalState(() -> session.start(PARAMS));
- verify(callback, times(1)).onStarted(any());
- }
-
- @Test
- public void testStop_CannotStopIfAlreadyStopped() throws RemoteException {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
- doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
- session.onRangingOpened();
- session.start(PARAMS);
-
- verifyNoThrowIllegalState(session::stop);
- verify(callback, times(1)).onStopped();
-
- // Calling stop again should throw an illegal state
- verifyThrowIllegalState(session::stop);
- verify(callback, times(1)).onStopped();
- }
-
- @Test
- public void testReconfigure_OnlyWhenOpened() throws RemoteException {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
- doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any());
-
- verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
- verify(callback, times(0)).onReconfigured(any());
- verifyOpenState(session, false);
-
- session.onRangingOpened();
- verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
- verify(callback, times(1)).onReconfigured(any());
- verifyOpenState(session, true);
-
- session.onRangingStarted(PARAMS);
- verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
- verify(callback, times(2)).onReconfigured(any());
- verifyOpenState(session, true);
-
- session.onRangingStopped();
- verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
- verify(callback, times(3)).onReconfigured(any());
- verifyOpenState(session, true);
-
-
- session.onRangingClosed(REASON, PARAMS);
- verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
- verify(callback, times(3)).onReconfigured(any());
- verifyOpenState(session, false);
- }
-
- @Test
- public void testClose_NoCallbackUntilInvoked() throws RemoteException {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- session.onRangingOpened();
-
- // Calling close multiple times should invoke closeRanging until the session receives
- // the onClosed callback.
- int totalCallsBeforeOnRangingClosed = 3;
- for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) {
- session.close();
- verifyOpenState(session, true);
- verify(adapter, times(i)).closeRanging(handle);
- verify(callback, times(0)).onClosed(anyInt(), any());
- }
-
- // After onClosed is invoked, then the adapter should no longer be called for each call to
- // the session's close.
- final int totalCallsAfterOnRangingClosed = 2;
- for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) {
- session.onRangingClosed(REASON, PARAMS);
- verifyOpenState(session, false);
- verify(adapter, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle);
- verify(callback, times(i)).onClosed(anyInt(), any());
- }
- }
-
- @Test
- public void testClose_OnClosedCalled() throws RemoteException {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
- session.onRangingOpened();
-
- session.close();
- verify(callback, times(1)).onClosed(anyInt(), any());
- }
-
- @Test
- public void testClose_CannotInteractFurther() throws RemoteException {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
- doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
- session.close();
-
- verifyThrowIllegalState(() -> session.start(PARAMS));
- verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
- verifyThrowIllegalState(() -> session.stop());
- verifyNoThrowIllegalState(() -> session.close());
- }
-
- @Test
- public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-
- assertFalse(session.isOpen());
- session.onRangingStarted(PARAMS);
- assertTrue(session.isOpen());
-
- // Verify that the onReportReceived callback was invoked
- RangingReport report = UwbTestUtils.getRangingReports(1);
- session.onRangingResult(report);
- verify(callback, times(1)).onReportReceived(report);
- }
-
- @Test
- public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
- SessionHandle handle = new SessionHandle(123);
- RangingSession.Callback callback = mock(RangingSession.Callback.class);
- IUwbAdapter adapter = mock(IUwbAdapter.class);
- RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-
- assertFalse(session.isOpen());
-
- // Verify that the onReportReceived callback was invoked
- RangingReport report = UwbTestUtils.getRangingReports(1);
- session.onRangingResult(report);
- verify(callback, times(0)).onReportReceived(report);
- }
-
- private void verifyOpenState(RangingSession session, boolean expected) {
- assertEquals(expected, session.isOpen());
- }
-
- private void verifyThrowIllegalState(Runnable runnable) {
- try {
- runnable.run();
- fail();
- } catch (IllegalStateException e) {
- // Pass
- }
- }
-
- private void verifyNoThrowIllegalState(Runnable runnable) {
- try {
- runnable.run();
- } catch (IllegalStateException e) {
- fail();
- }
- }
-
- abstract class AdapterAnswer implements Answer {
- protected RangingSession mSession;
-
- protected AdapterAnswer(RangingSession session) {
- mSession = session;
- }
- }
-
- class StartAnswer extends AdapterAnswer {
- StartAnswer(RangingSession session) {
- super(session);
- }
-
- @Override
- public Object answer(InvocationOnMock invocation) {
- mSession.onRangingStarted(PARAMS);
- return null;
- }
- }
-
- class ReconfigureAnswer extends AdapterAnswer {
- ReconfigureAnswer(RangingSession session) {
- super(session);
- }
-
- @Override
- public Object answer(InvocationOnMock invocation) {
- mSession.onRangingReconfigured(PARAMS);
- return null;
- }
- }
-
- class StopAnswer extends AdapterAnswer {
- StopAnswer(RangingSession session) {
- super(session);
- }
-
- @Override
- public Object answer(InvocationOnMock invocation) {
- mSession.onRangingStopped();
- return null;
- }
- }
-
- class CloseAnswer extends AdapterAnswer {
- CloseAnswer(RangingSession session) {
- super(session);
- }
-
- @Override
- public Object answer(InvocationOnMock invocation) {
- mSession.onRangingClosed(REASON, PARAMS);
- return null;
- }
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java b/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java
deleted file mode 100644
index 8b42ff7f62a7..000000000000
--- a/core/tests/uwbtests/src/android/uwb/SessionHandleTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link SessionHandle}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class SessionHandleTest {
-
- @Test
- public void testBasic() {
- int handleId = 12;
- SessionHandle handle = new SessionHandle(handleId);
- assertEquals(handle.getId(), handleId);
- }
-
- @Test
- public void testParcel() {
- Parcel parcel = Parcel.obtain();
- SessionHandle handle = new SessionHandle(10);
- handle.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- SessionHandle fromParcel = SessionHandle.CREATOR.createFromParcel(parcel);
- assertEquals(handle, fromParcel);
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/UwbAddressTest.java b/core/tests/uwbtests/src/android/uwb/UwbAddressTest.java
deleted file mode 100644
index ccc88a9a5399..000000000000
--- a/core/tests/uwbtests/src/android/uwb/UwbAddressTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link UwbAddress}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class UwbAddressTest {
-
- @Test
- public void testFromBytes_Short() {
- runFromBytes(UwbAddress.SHORT_ADDRESS_BYTE_LENGTH);
- }
-
- @Test
- public void testFromBytes_Extended() {
- runFromBytes(UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH);
- }
-
- private void runFromBytes(int len) {
- byte[] addressBytes = getByteArray(len);
- UwbAddress address = UwbAddress.fromBytes(addressBytes);
- assertEquals(address.size(), len);
- assertEquals(addressBytes, address.toBytes());
- }
-
- private byte[] getByteArray(int len) {
- byte[] res = new byte[len];
- for (int i = 0; i < len; i++) {
- res[i] = (byte) i;
- }
- return res;
- }
-
- @Test
- public void testParcel_Short() {
- runParcel(true);
- }
-
- @Test
- public void testParcel_Extended() {
- runParcel(false);
- }
-
- private void runParcel(boolean useShortAddress) {
- Parcel parcel = Parcel.obtain();
- UwbAddress address = UwbTestUtils.getUwbAddress(useShortAddress);
- address.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- UwbAddress fromParcel = UwbAddress.CREATOR.createFromParcel(parcel);
- assertEquals(address, fromParcel);
- }
-}
diff --git a/core/tests/uwbtests/src/android/uwb/UwbManagerTest.java b/core/tests/uwbtests/src/android/uwb/UwbManagerTest.java
deleted file mode 100644
index 4983bed742fd..000000000000
--- a/core/tests/uwbtests/src/android/uwb/UwbManagerTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.uwb;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import android.content.Context;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link UwbManager}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class UwbManagerTest {
-
- public final Context mContext = InstrumentationRegistry.getContext();
-
- @Test
- public void testServiceAvailable() {
- UwbManager manager = mContext.getSystemService(UwbManager.class);
- if (UwbTestUtils.isUwbSupported(mContext)) {
- assertNotNull(manager);
- } else {
- assertNull(manager);
- }
- }
-}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index adb8a54c0167..ea5fe39ccc08 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -23,7 +23,11 @@ import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.TestApi;
import android.app.ActivityThread;
+import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.media.metrics.PlaybackComponent;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -39,7 +43,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.time.Instant;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -144,6 +151,7 @@ public final class MediaDrm implements AutoCloseable {
private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
private long mNativeContext;
+ private final String mAppPackageName;
/**
* Specify no certificate type
@@ -281,8 +289,9 @@ public final class MediaDrm implements AutoCloseable {
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
+ mAppPackageName = ActivityThread.currentOpPackageName();
native_setup(new WeakReference<MediaDrm>(this),
- getByteArrayFromUUID(uuid), ActivityThread.currentOpPackageName());
+ getByteArrayFromUUID(uuid), mAppPackageName);
mCloseGuard.open("release");
}
@@ -1144,12 +1153,78 @@ public final class MediaDrm implements AutoCloseable {
* problem with the certifcate
*/
@NonNull
- public native KeyRequest getKeyRequest(
+ public KeyRequest getKeyRequest(
@NonNull byte[] scope, @Nullable byte[] init,
@Nullable String mimeType, @KeyType int keyType,
@Nullable HashMap<String, String> optionalParameters)
- throws NotProvisionedException;
+ throws NotProvisionedException {
+ HashMap<String, String> internalParams;
+ if (optionalParameters == null) {
+ internalParams = new HashMap<>();
+ } else {
+ internalParams = new HashMap<>(optionalParameters);
+ }
+ byte[] rawBytes = getNewestAvailablePackageCertificateRawBytes();
+ byte[] hashBytes = null;
+ if (rawBytes != null) {
+ hashBytes = getDigestBytes(rawBytes, "SHA-256");
+ }
+ if (hashBytes != null) {
+ Base64.Encoder encoderB64 = Base64.getEncoder();
+ String hashBytesB64 = encoderB64.encodeToString(hashBytes);
+ internalParams.put("package_certificate_hash_bytes", hashBytesB64);
+ }
+ return getKeyRequestNative(scope, init, mimeType, keyType, internalParams);
+ }
+ @Nullable
+ private byte[] getNewestAvailablePackageCertificateRawBytes() {
+ Application application = ActivityThread.currentApplication();
+ if (application == null) {
+ Log.w(TAG, "pkg cert: Application is null");
+ return null;
+ }
+ PackageManager pm = application.getPackageManager();
+ if (pm == null) {
+ Log.w(TAG, "pkg cert: PackageManager is null");
+ return null;
+ }
+ PackageInfo packageInfo = null;
+ try {
+ packageInfo = pm.getPackageInfo(mAppPackageName,
+ PackageManager.GET_SIGNING_CERTIFICATES);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, mAppPackageName, e);
+ }
+ if (packageInfo == null || packageInfo.signingInfo == null) {
+ Log.w(TAG, "pkg cert: PackageInfo or SigningInfo is null");
+ return null;
+ }
+ Signature[] signers = packageInfo.signingInfo.getApkContentsSigners();
+ if (signers != null && signers.length == 1) {
+ return signers[0].toByteArray();
+ }
+ Log.w(TAG, "pkg cert: " + signers.length + " signers");
+ return null;
+ }
+
+ @Nullable
+ private static byte[] getDigestBytes(@NonNull byte[] rawBytes, @NonNull String algorithm) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
+ return messageDigest.digest(rawBytes);
+ } catch (NoSuchAlgorithmException e) {
+ Log.w(TAG, algorithm, e);
+ }
+ return null;
+ }
+
+ @NonNull
+ private native KeyRequest getKeyRequestNative(
+ @NonNull byte[] scope, @Nullable byte[] init,
+ @Nullable String mimeType, @KeyType int keyType,
+ @Nullable HashMap<String, String> optionalParameters)
+ throws NotProvisionedException;
/**
* A key response is received from the license server by the app, then it is
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 740bc2dbeb43..c0185dcc4539 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2525,7 +2525,9 @@ public final class TvInputManager {
/**
* Pauses TV program recording in the current recording session.
*
- * @param params A set of extra parameters which might be handled with this event.
+ * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped
+ * name, i.e. prefixed with a package name you own, so that different developers
+ * will not create conflicting keys.
* {@link TvRecordingClient#pauseRecording(Bundle)}.
*/
void pauseRecording(@NonNull Bundle params) {
@@ -2543,7 +2545,9 @@ public final class TvInputManager {
/**
* Resumes TV program recording in the current recording session.
*
- * @param params A set of extra parameters which might be handled with this event.
+ * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped
+ * name, i.e. prefixed with a package name you own, so that different developers
+ * will not create conflicting keys.
* {@link TvRecordingClient#resumeRecording(Bundle)}.
*/
void resumeRecording(@NonNull Bundle params) {
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 22c3572e963b..d15e8485873d 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -2056,7 +2056,7 @@ static const JNINativeMethod gMethods[] = {
{ "closeSessionNative", "([B)V",
(void *)android_media_MediaDrm_closeSession },
- { "getKeyRequest", "([B[BLjava/lang/String;ILjava/util/HashMap;)"
+ { "getKeyRequestNative", "([B[BLjava/lang/String;ILjava/util/HashMap;)"
"Landroid/media/MediaDrm$KeyRequest;",
(void *)android_media_MediaDrm_getKeyRequest },
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index f6b239e31e99..ebb6e30d4b3b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -254,6 +254,7 @@ public class Task {
public Task(Task other) {
this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
other.isLocked, other.taskDescription, other.topActivity);
+ lastSnapshotData.set(other.lastSnapshotData);
}
/**
@@ -281,6 +282,25 @@ public class Task {
: key.baseIntent.getComponent();
}
+ public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) {
+ lastSnapshotData.set(rawTask.lastSnapshotData);
+ }
+
+ /**
+ * Returns the visible width to height ratio. Returns 0f if snapshot data is not available.
+ */
+ public float getVisibleThumbnailRatio() {
+ if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) {
+ return 0f;
+ }
+
+ float availableWidth = lastSnapshotData.taskSize.x - (lastSnapshotData.contentInsets.left
+ + lastSnapshotData.contentInsets.right);
+ float availableHeight = lastSnapshotData.taskSize.y - (lastSnapshotData.contentInsets.top
+ + lastSnapshotData.contentInsets.bottom);
+ return availableWidth / availableHeight;
+ }
+
@Override
public boolean equals(Object o) {
// Check that the id matches
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0ce26739b51c..5cb9d8ff3f31 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -70,6 +70,7 @@ import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.content.pm.IPackageLoadingProgressCallback;
import android.content.pm.InstallationFile;
import android.content.pm.InstallationFileParcel;
import android.content.pm.PackageInfo;
@@ -321,6 +322,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private float mProgress = 0;
@GuardedBy("mLock")
private float mReportedProgress = -1;
+ @GuardedBy("mLock")
+ private float mIncrementalProgress = 0;
/** State of the session. */
@GuardedBy("mLock")
@@ -3770,7 +3773,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir,
inheritedDir, params, statusListener, healthCheckParams, healthListener,
- addedFiles, perUidReadTimeouts);
+ addedFiles, perUidReadTimeouts,
+ new IPackageLoadingProgressCallback.Stub() {
+ @Override
+ public void onPackageLoadingProgressChanged(float progress) {
+ synchronized (mLock) {
+ mIncrementalProgress = progress;
+ }
+ }
+ });
return false;
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 96248c3711e3..0974537e2584 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -18,22 +18,65 @@ package com.android.server.speech;
import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.RemoteException;
import android.speech.IRecognitionListener;
import android.speech.IRecognitionService;
import android.speech.RecognitionService;
+import android.speech.SpeechRecognizer;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.ServiceConnector;
final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecognitionService> {
private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName();
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
- RemoteSpeechRecognitionService(Context context, ComponentName serviceName, int userId) {
+ private static final String APP_OP_MESSAGE = "Recording audio for speech recognition";
+ private static final String RECORD_AUDIO_APP_OP =
+ AppOpsManager.permissionToOp(android.Manifest.permission.RECORD_AUDIO);
+
+ private final Object mLock = new Object();
+
+ private boolean mConnected = false;
+
+ @Nullable
+ private IRecognitionListener mListener;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private String mPackageName;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private String mFeatureId;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private DelegatingListener mDelegatingListener;
+
+ // Makes sure we can block startListening() if session is still in progress.
+ @GuardedBy("mLock")
+ private boolean mSessionInProgress = false;
+
+ // Makes sure we call startProxyOp / finishProxyOp at right times and only once per session.
+ @GuardedBy("mLock")
+ private boolean mRecordingInProgress = false;
+
+ private final int mCallingUid;
+ private final AppOpsManager mAppOpsManager;
+ private final ComponentName mComponentName;
+
+ RemoteSpeechRecognitionService(
+ Context context, ComponentName serviceName, int userId, int callingUid) {
super(context,
new Intent(RecognitionService.SERVICE_INTERFACE).setComponent(serviceName),
Context.BIND_AUTO_CREATE
@@ -43,46 +86,197 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn
userId,
IRecognitionService.Stub::asInterface);
+ mCallingUid = callingUid;
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ mComponentName = serviceName;
+
if (DEBUG) {
Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString());
}
}
+ ComponentName getServiceComponentName() {
+ return mComponentName;
+ }
+
void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
- String featureId) throws RemoteException {
+ String featureId) {
if (DEBUG) {
- Slog.i(TAG, "#startListening for package: " + packageName + ", feature=" + featureId);
+ Slog.i(TAG, String.format("#startListening for package: %s, feature=%s, callingUid=%d",
+ packageName, featureId, mCallingUid));
+ }
+
+ if (listener == null) {
+ Log.w(TAG, "#startListening called with no preceding #setListening - ignoring");
+ return;
+ }
+
+ if (!mConnected) {
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (mSessionInProgress) {
+ Slog.i(TAG, "#startListening called while listening is in progress.");
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+ return;
+ }
+
+ if (startProxyOp(packageName, featureId) != AppOpsManager.MODE_ALLOWED) {
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+ return;
+ }
+ mSessionInProgress = true;
+ mRecordingInProgress = true;
+
+ mListener = listener;
+ mDelegatingListener = new DelegatingListener(listener, () -> {
+ // To be invoked in terminal calls of the callback: results() or error()
+ if (DEBUG) {
+ Slog.i(TAG, "Recognition session complete");
+ }
+
+ synchronized (mLock) {
+ resetStateLocked();
+ }
+ });
+ mPackageName = packageName;
+ mFeatureId = featureId;
+
+ run(service ->
+ service.startListening(
+ recognizerIntent,
+ mDelegatingListener,
+ packageName,
+ featureId,
+ mCallingUid));
}
- run(service -> service.startListening(recognizerIntent, listener, packageName, featureId));
}
- void stopListening(IRecognitionListener listener, String packageName, String featureId)
- throws RemoteException {
+ void stopListening(
+ IRecognitionListener listener, String packageName, String featureId) {
if (DEBUG) {
Slog.i(TAG, "#stopListening for package: " + packageName + ", feature=" + featureId);
}
- run(service -> service.stopListening(listener, packageName, featureId));
+
+ if (!mConnected) {
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (mListener == null) {
+ Log.w(TAG, "#stopListening called with no preceding #startListening - ignoring");
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+ return;
+ }
+
+ if (mListener.asBinder() != listener.asBinder()) {
+ Log.w(TAG, "#stopListening called with an unexpected listener");
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+ return;
+ }
+
+ if (!mRecordingInProgress) {
+ Slog.i(TAG, "#stopListening called while listening isn't in progress, ignoring.");
+ return;
+ }
+ mRecordingInProgress = false;
+
+ finishProxyOp(packageName, featureId);
+
+ run(service -> service.stopListening(mDelegatingListener, packageName, featureId));
+ }
}
- void cancel(IRecognitionListener listener, String packageName, String featureId)
- throws RemoteException {
+ void cancel(
+ IRecognitionListener listener,
+ String packageName,
+ String featureId,
+ boolean isShutdown) {
if (DEBUG) {
Slog.i(TAG, "#cancel for package: " + packageName + ", feature=" + featureId);
}
- run(service -> service.cancel(listener, packageName, featureId));
+
+ if (!mConnected) {
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+ }
+
+ synchronized (mLock) {
+ if (mListener == null) {
+ if (DEBUG) {
+ Log.w(TAG, "#cancel called with no preceding #startListening - ignoring");
+ }
+ return;
+ }
+
+ if (mListener.asBinder() != listener.asBinder()) {
+ Log.w(TAG, "#cancel called with an unexpected listener");
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+ return;
+ }
+
+ // Temporary reference to allow for resetting the hard link mDelegatingListener to null.
+ IRecognitionListener delegatingListener = mDelegatingListener;
+
+ run(service -> service.cancel(delegatingListener, packageName, featureId, isShutdown));
+
+ if (mRecordingInProgress) {
+ finishProxyOp(packageName, featureId);
+ }
+ mRecordingInProgress = false;
+ mSessionInProgress = false;
+
+ mDelegatingListener = null;
+ mListener = null;
+
+ // Schedule to unbind after cancel is delivered.
+ if (isShutdown) {
+ run(service -> unbind());
+ }
+ }
+ }
+
+ void shutdown() {
+ synchronized (mLock) {
+ if (this.mListener == null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Package died, but session wasn't initialized. "
+ + "Not invoking #cancel");
+ }
+ return;
+ }
+ }
+
+ cancel(mListener, mPackageName, mFeatureId, true /* isShutdown */);
}
@Override // from ServiceConnector.Impl
protected void onServiceConnectionStatusChanged(
IRecognitionService service, boolean connected) {
- if (!DEBUG) {
- return;
+ mConnected = connected;
+
+ if (DEBUG) {
+ if (connected) {
+ Slog.i(TAG, "Connected to speech recognition service");
+ } else {
+ Slog.w(TAG, "Disconnected from speech recognition service");
+ }
}
- if (connected) {
- Slog.i(TAG, "Connected to ASR service");
- } else {
- Slog.w(TAG, "Disconnected from ASR service");
+ synchronized (mLock) {
+ if (!connected) {
+ if (mListener == null) {
+ Slog.i(TAG, "Connection to speech recognition service lost, but no "
+ + "#startListening has been invoked yet.");
+ return;
+ }
+
+ tryRespondWithError(mListener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+
+ resetStateLocked();
+ }
}
}
@@ -90,4 +284,119 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn
protected long getAutoDisconnectTimeoutMs() {
return PERMANENT_BOUND_TIMEOUT_MS;
}
+
+ private void resetStateLocked() {
+ if (mRecordingInProgress && mPackageName != null && mFeatureId != null) {
+ finishProxyOp(mPackageName, mFeatureId);
+ }
+
+ mListener = null;
+ mDelegatingListener = null;
+ mSessionInProgress = false;
+ mRecordingInProgress = false;
+ }
+
+ private int startProxyOp(String packageName, String featureId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mAppOpsManager.startProxyOp(
+ RECORD_AUDIO_APP_OP,
+ mCallingUid,
+ packageName,
+ featureId,
+ APP_OP_MESSAGE);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void finishProxyOp(String packageName, String featureId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mAppOpsManager.finishProxyOp(
+ RECORD_AUDIO_APP_OP, mCallingUid, packageName, featureId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private static void tryRespondWithError(IRecognitionListener listener, int errorCode) {
+ if (DEBUG) {
+ Slog.i(TAG, "Responding with error " + errorCode);
+ }
+
+ try {
+ if (listener != null) {
+ listener.onError(errorCode);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG,
+ String.format("Failed to respond with an error %d to the client", errorCode),
+ e);
+ }
+ }
+
+ private static class DelegatingListener extends IRecognitionListener.Stub {
+
+ private final IRecognitionListener mRemoteListener;
+ private final Runnable mOnSessionComplete;
+
+ DelegatingListener(IRecognitionListener listener, Runnable onSessionComplete) {
+ mRemoteListener = listener;
+ mOnSessionComplete = onSessionComplete;
+ }
+
+ @Override
+ public void onReadyForSpeech(Bundle params) throws RemoteException {
+ mRemoteListener.onReadyForSpeech(params);
+ }
+
+ @Override
+ public void onBeginningOfSpeech() throws RemoteException {
+ mRemoteListener.onBeginningOfSpeech();
+ }
+
+ @Override
+ public void onRmsChanged(float rmsdB) throws RemoteException {
+ mRemoteListener.onRmsChanged(rmsdB);
+ }
+
+ @Override
+ public void onBufferReceived(byte[] buffer) throws RemoteException {
+ mRemoteListener.onBufferReceived(buffer);
+ }
+
+ @Override
+ public void onEndOfSpeech() throws RemoteException {
+ mRemoteListener.onEndOfSpeech();
+ }
+
+ @Override
+ public void onError(int error) throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, String.format("Error %d during recognition session", error));
+ }
+ mOnSessionComplete.run();
+ mRemoteListener.onError(error);
+ }
+
+ @Override
+ public void onResults(Bundle results) throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, "#onResults invoked for a recognition session");
+ }
+ mOnSessionComplete.run();
+ mRemoteListener.onResults(results);
+ }
+
+ @Override
+ public void onPartialResults(Bundle results) throws RemoteException {
+ mRemoteListener.onPartialResults(results);
+ }
+
+ @Override
+ public void onEvent(int eventType, Bundle params) throws RemoteException {
+ mRemoteListener.onEvent(eventType, params);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
index 592ba9e616b8..dbe73546d748 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
@@ -18,7 +18,9 @@ package com.android.server.speech;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
import android.content.Context;
+import android.os.IBinder;
import android.os.UserHandle;
import android.speech.IRecognitionServiceManager;
import android.speech.IRecognitionServiceManagerCallback;
@@ -42,6 +44,7 @@ public final class SpeechRecognitionManagerService extends
public SpeechRecognitionManagerService(@NonNull Context context) {
super(context,
+ // TODO(b/176578753): think if we want to favor the particular service here.
new FrameworkResourcesServiceNameResolver(
context,
R.string.config_defaultOnDeviceSpeechRecognitionService),
@@ -63,11 +66,15 @@ public final class SpeechRecognitionManagerService extends
final class SpeechRecognitionManagerServiceStub extends IRecognitionServiceManager.Stub {
@Override
- public void createSession(IRecognitionServiceManagerCallback callback) {
+ public void createSession(
+ ComponentName componentName,
+ IBinder clientToken,
+ boolean onDevice,
+ IRecognitionServiceManagerCallback callback) {
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
SpeechRecognitionManagerServiceImpl service = getServiceForUserLocked(userId);
- service.createSessionLocked(callback);
+ service.createSessionLocked(componentName, clientToken, onDevice, callback);
}
}
}
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bcaf174b1d92..2656a3d32555 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -24,30 +24,44 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
import android.speech.IRecognitionListener;
import android.speech.IRecognitionService;
import android.speech.IRecognitionServiceManagerCallback;
+import android.speech.SpeechRecognizer;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.infra.AbstractPerUserSystemService;
+import com.google.android.collect.Sets;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
final class SpeechRecognitionManagerServiceImpl extends
AbstractPerUserSystemService<SpeechRecognitionManagerServiceImpl,
SpeechRecognitionManagerService> {
-
private static final String TAG = SpeechRecognitionManagerServiceImpl.class.getSimpleName();
+ private static final int MAX_CONCURRENT_CONNECTIONS_BY_CLIENT = 10;
+
+ private final Object mLock = new Object();
+
+ @NonNull
@GuardedBy("mLock")
- @Nullable
- private RemoteSpeechRecognitionService mRemoteService;
+ private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid =
+ new HashMap<>();
SpeechRecognitionManagerServiceImpl(
@NonNull SpeechRecognitionManagerService master,
@NonNull Object lock, @UserIdInt int userId, boolean disabled) {
super(master, lock, userId);
- updateRemoteServiceLocked();
}
@GuardedBy("mLock")
@@ -67,92 +81,196 @@ final class SpeechRecognitionManagerServiceImpl extends
@Override // from PerUserSystemService
protected boolean updateLocked(boolean disabled) {
final boolean enabledChanged = super.updateLocked(disabled);
- updateRemoteServiceLocked();
return enabledChanged;
}
- /**
- * Updates the reference to the remote service.
- */
- @GuardedBy("mLock")
- private void updateRemoteServiceLocked() {
- if (mRemoteService != null) {
- if (mMaster.debug) {
- Slog.d(TAG, "updateRemoteService(): destroying old remote service");
- }
- mRemoteService.unbind();
- mRemoteService = null;
+ void createSessionLocked(
+ ComponentName componentName,
+ IBinder clientToken,
+ boolean onDevice,
+ IRecognitionServiceManagerCallback callback) {
+ if (mMaster.debug) {
+ Slog.i(TAG, String.format("#createSessionLocked, component=%s, onDevice=%s",
+ componentName, onDevice));
+ }
+
+ ComponentName serviceComponent = componentName;
+ if (onDevice) {
+ serviceComponent = getOnDeviceComponentNameLocked();
}
- }
- void createSessionLocked(IRecognitionServiceManagerCallback callback) {
- // TODO(b/176578753): check clients have record audio permission.
- // TODO(b/176578753): verify caller package is the one supplied
+ if (serviceComponent == null) {
+ tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT);
+ return;
+ }
- RemoteSpeechRecognitionService service = ensureRemoteServiceLocked();
+ final int creatorCallingUid = Binder.getCallingUid();
+ Set<String> creatorPackageNames =
+ Sets.newArraySet(
+ getContext().getPackageManager().getPackagesForUid(creatorCallingUid));
+
+ RemoteSpeechRecognitionService service = createService(creatorCallingUid, serviceComponent);
if (service == null) {
- tryRespondWithError(callback);
+ tryRespondWithError(callback, SpeechRecognizer.ERROR_TOO_MANY_REQUESTS);
return;
}
+ IBinder.DeathRecipient deathRecipient =
+ () -> handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+
+ try {
+ clientToken.linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ // RemoteException == binder already died, schedule disconnect anyway.
+ handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+ }
+
service.connect().thenAccept(binderService -> {
if (binderService != null) {
try {
callback.onSuccess(new IRecognitionService.Stub() {
@Override
- public void startListening(Intent recognizerIntent,
+ public void startListening(
+ Intent recognizerIntent,
IRecognitionListener listener,
- String packageName, String featureId) throws RemoteException {
+ String packageName,
+ String featureId,
+ int callingUid) throws RemoteException {
+ verifyCallerIdentity(
+ creatorCallingUid, packageName, creatorPackageNames, listener);
+ if (callingUid != creatorCallingUid) {
+ listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+ return;
+ }
+
service.startListening(
recognizerIntent, listener, packageName, featureId);
}
@Override
- public void stopListening(IRecognitionListener listener,
+ public void stopListening(
+ IRecognitionListener listener,
String packageName,
String featureId) throws RemoteException {
+ verifyCallerIdentity(
+ creatorCallingUid, packageName, creatorPackageNames, listener);
+
service.stopListening(listener, packageName, featureId);
}
@Override
- public void cancel(IRecognitionListener listener,
+ public void cancel(
+ IRecognitionListener listener,
String packageName,
- String featureId) throws RemoteException {
- service.cancel(listener, packageName, featureId);
+ String featureId,
+ boolean isShutdown) throws RemoteException {
+ verifyCallerIdentity(
+ creatorCallingUid, packageName, creatorPackageNames, listener);
+
+ service.cancel(listener, packageName, featureId, isShutdown);
+
+ if (isShutdown) {
+ handleClientDeath(
+ creatorCallingUid,
+ service,
+ false /* invoke #cancel */);
+ clientToken.unlinkToDeath(deathRecipient, 0);
+ }
}
});
} catch (RemoteException e) {
Slog.e(TAG, "Error creating a speech recognition session", e);
- tryRespondWithError(callback);
+ tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT);
}
} else {
- tryRespondWithError(callback);
+ tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT);
}
});
}
+ private void verifyCallerIdentity(
+ int creatorCallingUid,
+ String packageName,
+ Set<String> creatorPackageNames,
+ IRecognitionListener listener) throws RemoteException {
+ if (creatorCallingUid != Binder.getCallingUid()
+ || !creatorPackageNames.contains(packageName)) {
+ listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+ }
+ }
+
+ private void handleClientDeath(
+ int callingUid,
+ RemoteSpeechRecognitionService service, boolean invokeCancel) {
+ if (invokeCancel) {
+ service.shutdown();
+ }
+ removeService(callingUid, service);
+ }
+
@GuardedBy("mLock")
@Nullable
- private RemoteSpeechRecognitionService ensureRemoteServiceLocked() {
- if (mRemoteService == null) {
- final String serviceName = getComponentNameLocked();
- if (serviceName == null) {
- if (mMaster.verbose) {
- Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
- }
+ private ComponentName getOnDeviceComponentNameLocked() {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
+ }
+ return null;
+ }
+ return ComponentName.unflattenFromString(serviceName);
+ }
+
+ private RemoteSpeechRecognitionService createService(
+ int callingUid, ComponentName serviceComponent) {
+ synchronized (mLock) {
+ Set<RemoteSpeechRecognitionService> servicesForClient =
+ mRemoteServicesByUid.get(callingUid);
+
+ if (servicesForClient != null
+ && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
return null;
}
- final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
- mRemoteService =
- new RemoteSpeechRecognitionService(getContext(), serviceComponent, mUserId);
+
+ if (servicesForClient != null) {
+ Optional<RemoteSpeechRecognitionService> existingService =
+ servicesForClient
+ .stream()
+ .filter(service ->
+ service.getServiceComponentName().equals(serviceComponent))
+ .findFirst();
+ if (existingService.isPresent()) {
+ return existingService.get();
+ }
+ }
+
+ RemoteSpeechRecognitionService service =
+ new RemoteSpeechRecognitionService(
+ getContext(), serviceComponent, getUserId(), callingUid);
+
+ Set<RemoteSpeechRecognitionService> valuesByCaller =
+ mRemoteServicesByUid.computeIfAbsent(callingUid, key -> new HashSet<>());
+ valuesByCaller.add(service);
+
+ return service;
+ }
+ }
+
+ private void removeService(int callingUid, RemoteSpeechRecognitionService service) {
+ synchronized (mLock) {
+ Set<RemoteSpeechRecognitionService> valuesByCaller =
+ mRemoteServicesByUid.get(callingUid);
+ if (valuesByCaller != null) {
+ valuesByCaller.remove(service);
+ }
}
- return mRemoteService;
}
- private static void tryRespondWithError(IRecognitionServiceManagerCallback callback) {
+ private static void tryRespondWithError(IRecognitionServiceManagerCallback callback,
+ int errorCode) {
try {
- callback.onError();
+ callback.onError(errorCode);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to respond with error");
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1cf4ce163640..dd599c9cfb60 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10092,7 +10092,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final int userId = user.id;
- // TODO(b/177547285): add CTS test
if (mInjector.userManagerIsHeadlessSystemUserMode()) {
ComponentName admin = mOwners.getDeviceOwnerComponent();
Slog.i(LOG_TAG, "Automatically setting profile owner (" + admin + ") on new user "
@@ -12911,8 +12910,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (mOwners.hasProfileOwner(deviceOwnerUserId)) {
return CODE_USER_HAS_PROFILE_OWNER;
}
+
+ boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
// System user is always running in headless system user mode.
- if (!mInjector.userManagerIsHeadlessSystemUserMode()
+ if (!isHeadlessSystemUserMode
&& !mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
return CODE_USER_NOT_RUNNING;
}
@@ -12920,7 +12921,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return CODE_HAS_PAIRED;
}
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+ if (isHeadlessSystemUserMode) {
if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
Slog.e(LOG_TAG, "In headless system user mode, "
+ "device owner can only be set on headless system user.");
@@ -12932,9 +12933,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// If shell command runs after user setup completed check device status. Otherwise, OK.
if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
// In non-headless system user mode, DO can be setup only if
- // there's no non-system user
- if (!mInjector.userManagerIsHeadlessSystemUserMode()
- && mUserManager.getUserCount() > 1) {
+ // there's no non-system user.
+ // In headless system user mode, DO can be setup only if there are
+ // two users: the headless system user and the foreground user.
+ // If there could be multiple foreground users, this constraint should be modified.
+
+ int maxNumberOfExistingUsers = isHeadlessSystemUserMode ? 2 : 1;
+ if (mUserManager.getUserCount() > maxNumberOfExistingUsers) {
return CODE_NONSYSTEM_USER_EXISTS;
}
diff --git a/tests/FlickerTests/OWNERS b/tests/FlickerTests/OWNERS
index f35a318acbf7..b5561010e7f9 100644
--- a/tests/FlickerTests/OWNERS
+++ b/tests/FlickerTests/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 909476
include /services/core/java/com/android/server/wm/OWNERS
natanieljr@google.com \ No newline at end of file