Merge "AmbientContextManager API changes to support quick-tap detection" into tm-qpr-dev

This commit is contained in:
Lili Zou
2022-08-31 13:41:34 +00:00
committed by Android (Google) Code Review
9 changed files with 295 additions and 96 deletions

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.ambientcontext;
import android.annotation.NonNull;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Callback for listening to Ambient Context events and status changes. See {@link
* AmbientContextManager#registerObserver(AmbientContextEventRequest, AmbientContextCallback,
* Executor)}
*
* @hide
*/
public interface AmbientContextCallback {
/**
* Called when AmbientContextManager service detects events.
*
* @param events a list of detected events.
*/
void onEvents(@NonNull List<AmbientContextEvent> events);
/**
* Called with a statusCode when
* {@link AmbientContextManager#registerObserver(AmbientContextEventRequest,
* Executor, AmbientContextCallback)} completes, to indicate if the registration is successful
*
* @param statusCode the status of the service.
*/
void onRegistrationComplete(@NonNull @AmbientContextManager.StatusCode int statusCode);
}

View File

@@ -59,11 +59,21 @@ public final class AmbientContextEvent implements Parcelable {
*/ */
public static final int EVENT_SNORE = 2; public static final int EVENT_SNORE = 2;
/**
* The integer indicating a double-tap event was detected.
* For detecting this event type, there's no specific consent activity to request access, but
* the consent is implied through the double tap toggle in the Settings app.
*
* @hide
*/
public static final int EVENT_BACK_DOUBLE_TAP = 3;
/** @hide */ /** @hide */
@IntDef(prefix = { "EVENT_" }, value = { @IntDef(prefix = { "EVENT_" }, value = {
EVENT_UNKNOWN, EVENT_UNKNOWN,
EVENT_COUGH, EVENT_COUGH,
EVENT_SNORE, EVENT_SNORE,
EVENT_BACK_DOUBLE_TAP,
}) public @interface EventCode {} }) public @interface EventCode {}
/** The integer indicating an unknown level. */ /** The integer indicating an unknown level. */
@@ -150,7 +160,8 @@ public final class AmbientContextEvent implements Parcelable {
@IntDef(prefix = "EVENT_", value = { @IntDef(prefix = "EVENT_", value = {
EVENT_UNKNOWN, EVENT_UNKNOWN,
EVENT_COUGH, EVENT_COUGH,
EVENT_SNORE EVENT_SNORE,
EVENT_BACK_DOUBLE_TAP
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member @DataClass.Generated.Member
@@ -166,6 +177,8 @@ public final class AmbientContextEvent implements Parcelable {
return "EVENT_COUGH"; return "EVENT_COUGH";
case EVENT_SNORE: case EVENT_SNORE:
return "EVENT_SNORE"; return "EVENT_SNORE";
case EVENT_BACK_DOUBLE_TAP:
return "EVENT_BACK_DOUBLE_TAP";
default: return Integer.toHexString(value); default: return Integer.toHexString(value);
} }
} }
@@ -478,10 +491,10 @@ public final class AmbientContextEvent implements Parcelable {
} }
@DataClass.Generated( @DataClass.Generated(
time = 1642040319323L, time = 1659950304931L,
codegenVersion = "1.0.23", codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java", sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
@Deprecated @Deprecated
private void __metadata() {} private void __metadata() {}

View File

@@ -282,7 +282,7 @@ public final class AmbientContextManager {
Preconditions.checkArgument(!resultPendingIntent.isImmutable()); Preconditions.checkArgument(!resultPendingIntent.isImmutable());
try { try {
RemoteCallback callback = new RemoteCallback(result -> { RemoteCallback callback = new RemoteCallback(result -> {
int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
final long identity = Binder.clearCallingIdentity(); final long identity = Binder.clearCallingIdentity();
try { try {
executor.execute(() -> statusConsumer.accept(statusCode)); executor.execute(() -> statusConsumer.accept(statusCode));
@@ -296,6 +296,72 @@ public final class AmbientContextManager {
} }
} }
/**
* Allows app to register as a {@link AmbientContextEvent} observer. Same as {@link
* #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)},
* but use {@link AmbientContextCallback} instead of {@link PendingIntent} as a callback on
* detected events.
* Registering another observer from the same package that has already been
* registered will override the previous observer. If the same app previously calls
* {@link #registerObserver(AmbientContextEventRequest, AmbientContextCallback, Executor)},
* and now calls
* {@link #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)},
* the previous observer will be replaced with the new observer with the PendingIntent callback.
* Or vice versa.
*
* When the registration completes, a status will be returned to client through
* {@link AmbientContextCallback#onRegistrationComplete(int)}.
* If the AmbientContextManager service is not enabled yet, or the underlying detection service
* is not running yet, {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE} will be
* returned, and the detection won't be really started.
* If the underlying detection service feature is not enabled, or the requested event type is
* not enabled yet, {@link AmbientContextManager#STATUS_NOT_SUPPORTED} will be returned, and the
* detection won't be really started.
* If there is no user consent, {@link AmbientContextManager#STATUS_ACCESS_DENIED} will be
* returned, and the detection won't be really started.
* Otherwise, it will try to start the detection. And if it starts successfully, it will return
* {@link AmbientContextManager#STATUS_SUCCESS}. If it fails to start the detection, then
* it will return {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE}
* After registerObserver succeeds and when the service detects an event, the service will
* trigger {@link AmbientContextCallback#onEvents(List)}.
*
* @hide
*/
@RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
public void registerObserver(
@NonNull AmbientContextEventRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull AmbientContextCallback ambientContextCallback) {
try {
IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
@Override
public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> ambientContextCallback.onEvents(events));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onRegistrationComplete(int statusCode) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(
() -> ambientContextCallback.onRegistrationComplete(statusCode));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
};
mService.registerObserverWithCallback(request, mContext.getPackageName(), observer);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** /**
* Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an
* observer that was already unregistered or never registered will have no effect. * observer that was already unregistered or never registered will have no effect.

View File

@@ -18,6 +18,7 @@ package android.app.ambientcontext;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.IAmbientContextObserver;
import android.os.RemoteCallback; import android.os.RemoteCallback;
/** /**
@@ -29,6 +30,11 @@ interface IAmbientContextManager {
void registerObserver(in AmbientContextEventRequest request, void registerObserver(in AmbientContextEventRequest request,
in PendingIntent resultPendingIntent, in PendingIntent resultPendingIntent,
in RemoteCallback statusCallback); in RemoteCallback statusCallback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)")
void registerObserverWithCallback(in AmbientContextEventRequest request,
String packageName,
in IAmbientContextObserver observer);
void unregisterObserver(in String callingPackage); void unregisterObserver(in String callingPackage);
void queryServiceStatus(in int[] eventTypes, in String callingPackage, void queryServiceStatus(in int[] eventTypes, in String callingPackage,
in RemoteCallback statusCallback); in RemoteCallback statusCallback);

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.ambientcontext;
import android.app.ambientcontext.AmbientContextEvent;
/**
* Callback interface of AmbientContextManager.
*
* @hide
*/
oneway interface IAmbientContextObserver {
void onEvents(in List<AmbientContextEvent> events);
void onRegistrationComplete(in int statusCode);
}

View File

@@ -29,6 +29,7 @@ import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.AmbientContextManager; import android.app.ambientcontext.AmbientContextManager;
import android.app.ambientcontext.IAmbientContextObserver;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@@ -53,6 +54,8 @@ import com.android.server.infra.AbstractPerUserSystemService;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
/** /**
* Per-user manager service for {@link AmbientContextEvent}s. * Per-user manager service for {@link AmbientContextEvent}s.
@@ -165,20 +168,17 @@ final class AmbientContextManagerPerUserService extends
* package. A new registration from the same package will overwrite the previous registration. * package. A new registration from the same package will overwrite the previous registration.
*/ */
public void onRegisterObserver(AmbientContextEventRequest request, public void onRegisterObserver(AmbientContextEventRequest request,
PendingIntent pendingIntent, RemoteCallback clientStatusCallback) { String packageName, IAmbientContextObserver observer) {
synchronized (mLock) { synchronized (mLock) {
if (!setUpServiceIfNeeded()) { if (!setUpServiceIfNeeded()) {
Slog.w(TAG, "Detection service is not available at this moment."); Slog.w(TAG, "Detection service is not available at this moment.");
sendStatusCallback( completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
clientStatusCallback,
AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
return; return;
} }
// Register package and add to existing ClientRequests cache // Register package and add to existing ClientRequests cache
startDetection(request, pendingIntent.getCreatorPackage(), startDetection(request, packageName, observer);
createDetectionResultRemoteCallback(), clientStatusCallback); mMaster.newClientAdded(mUserId, request, packageName, observer);
mMaster.newClientAdded(mUserId, request, pendingIntent, clientStatusCallback);
} }
} }
@@ -186,49 +186,46 @@ final class AmbientContextManagerPerUserService extends
* Returns a RemoteCallback that handles the status from the detection service, and * Returns a RemoteCallback that handles the status from the detection service, and
* sends results to the client callback. * sends results to the client callback.
*/ */
private RemoteCallback getServerStatusCallback(RemoteCallback clientStatusCallback) { private RemoteCallback getServerStatusCallback(Consumer<Integer> statusConsumer) {
return new RemoteCallback(result -> { return new RemoteCallback(result -> {
AmbientContextDetectionServiceStatus serviceStatus = AmbientContextDetectionServiceStatus serviceStatus =
(AmbientContextDetectionServiceStatus) result.get( (AmbientContextDetectionServiceStatus) result.get(
AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY); AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
final long token = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity();
try { try {
String packageName = serviceStatus.getPackageName();
Bundle bundle = new Bundle();
bundle.putInt(
AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
serviceStatus.getStatusCode());
clientStatusCallback.sendResult(bundle);
int statusCode = serviceStatus.getStatusCode(); int statusCode = serviceStatus.getStatusCode();
statusConsumer.accept(statusCode);
Slog.i(TAG, "Got detection status of " + statusCode Slog.i(TAG, "Got detection status of " + statusCode
+ " for " + packageName); + " for " + serviceStatus.getPackageName());
} finally { } finally {
Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token);
} }
}); });
} }
@VisibleForTesting
void startDetection(AmbientContextEventRequest request, String callingPackage, void startDetection(AmbientContextEventRequest request, String callingPackage,
RemoteCallback detectionResultCallback, RemoteCallback clientStatusCallback) { IAmbientContextObserver observer) {
Slog.d(TAG, "Requested detection of " + request.getEventTypes()); Slog.d(TAG, "Requested detection of " + request.getEventTypes());
synchronized (mLock) { synchronized (mLock) {
if (setUpServiceIfNeeded()) { if (setUpServiceIfNeeded()) {
ensureRemoteServiceInitiated(); ensureRemoteServiceInitiated();
mRemoteService.startDetection(request, callingPackage, detectionResultCallback, mRemoteService.startDetection(request, callingPackage,
getServerStatusCallback(clientStatusCallback)); createDetectionResultRemoteCallback(),
getServerStatusCallback(
statusCode -> completeRegistration(observer, statusCode)));
} else { } else {
Slog.w(TAG, "No valid component found for AmbientContextDetectionService"); Slog.w(TAG, "No valid component found for AmbientContextDetectionService");
sendStatusToCallback(clientStatusCallback, completeRegistration(observer,
AmbientContextManager.STATUS_NOT_SUPPORTED); AmbientContextManager.STATUS_NOT_SUPPORTED);
} }
} }
} }
/** /**
* Sends an intent with a status code and empty events. * Sends the result response with the specified status to the callback.
*/ */
void sendStatusCallback(RemoteCallback statusCallback, int statusCode) { static void sendStatusCallback(RemoteCallback statusCallback,
@AmbientContextManager.StatusCode int statusCode) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putInt( bundle.putInt(
AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
@@ -236,6 +233,15 @@ final class AmbientContextManagerPerUserService extends
statusCallback.sendResult(bundle); statusCallback.sendResult(bundle);
} }
static void completeRegistration(IAmbientContextObserver observer, int statusCode) {
try {
observer.onRegistrationComplete(statusCode);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call IAmbientContextObserver.onRegistrationComplete: "
+ e.getMessage());
}
}
/** /**
* Unregisters the client from all previously registered events by removing from the * Unregisters the client from all previously registered events by removing from the
* mExistingRequests map, and unregister events from the service if those events are not * mExistingRequests map, and unregister events from the service if those events are not
@@ -255,7 +261,7 @@ final class AmbientContextManagerPerUserService extends
synchronized (mLock) { synchronized (mLock) {
if (!setUpServiceIfNeeded()) { if (!setUpServiceIfNeeded()) {
Slog.w(TAG, "Detection service is not available at this moment."); Slog.w(TAG, "Detection service is not available at this moment.");
sendStatusToCallback(statusCallback, sendStatusCallback(statusCallback,
AmbientContextManager.STATUS_NOT_SUPPORTED); AmbientContextManager.STATUS_NOT_SUPPORTED);
return; return;
} }
@@ -263,7 +269,8 @@ final class AmbientContextManagerPerUserService extends
mRemoteService.queryServiceStatus( mRemoteService.queryServiceStatus(
eventTypes, eventTypes,
callingPackage, callingPackage,
getServerStatusCallback(statusCallback)); getServerStatusCallback(
statusCode -> sendStatusCallback(statusCallback, statusCode)));
} }
} }
@@ -350,18 +357,6 @@ final class AmbientContextManagerPerUserService extends
return ComponentName.unflattenFromString(consentComponent); return ComponentName.unflattenFromString(consentComponent);
} }
/**
* Sends the result response with the specified status to the callback.
*/
void sendStatusToCallback(RemoteCallback callback,
@AmbientContextManager.StatusCode int status) {
Bundle bundle = new Bundle();
bundle.putInt(
AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
status);
callback.sendResult(bundle);
}
@VisibleForTesting @VisibleForTesting
void stopDetection(String packageName) { void stopDetection(String packageName) {
Slog.d(TAG, "Stop detection for " + packageName); Slog.d(TAG, "Stop detection for " + packageName);
@@ -377,13 +372,13 @@ final class AmbientContextManagerPerUserService extends
* Sends out the Intent to the client after the event is detected. * Sends out the Intent to the client after the event is detected.
* *
* @param pendingIntent Client's PendingIntent for callback * @param pendingIntent Client's PendingIntent for callback
* @param result result from the detection service * @param events detected events from the detection service
*/ */
private void sendDetectionResultIntent(PendingIntent pendingIntent, void sendDetectionResultIntent(PendingIntent pendingIntent,
AmbientContextDetectionResult result) { List<AmbientContextEvent> events) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS, intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS,
new ArrayList(result.getEvents())); new ArrayList(events));
// Explicitly disallow the receiver from starting activities, to prevent apps from utilizing // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing
// the PendingIntent as a backdoor to do this. // the PendingIntent as a backdoor to do this.
BroadcastOptions options = BroadcastOptions.makeBasic(); BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -392,7 +387,7 @@ final class AmbientContextManagerPerUserService extends
pendingIntent.send(getContext(), 0, intent, null, null, null, pendingIntent.send(getContext(), 0, intent, null, null, null,
options.toBundle()); options.toBundle());
Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": " Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
+ result); + events);
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent); Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent);
} }
@@ -405,16 +400,19 @@ final class AmbientContextManagerPerUserService extends
(AmbientContextDetectionResult) result.get( (AmbientContextDetectionResult) result.get(
AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY); AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
String packageName = detectionResult.getPackageName(); String packageName = detectionResult.getPackageName();
PendingIntent pendingIntent = mMaster.getPendingIntent(mUserId, packageName); IAmbientContextObserver observer = mMaster.getClientRequestObserver(
if (pendingIntent == null) { mUserId, packageName);
if (observer == null) {
return; return;
} }
final long token = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity();
try { try {
sendDetectionResultIntent(pendingIntent, detectionResult); observer.onEvents(detectionResult.getEvents());
Slog.i(TAG, "Got detection result of " + detectionResult.getEvents() Slog.i(TAG, "Got detection result of " + detectionResult.getEvents()
+ " for " + packageName); + " for " + packageName);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call IAmbientContextObserver.onEvents: " + e.getMessage());
} finally { } finally {
Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token);
} }

View File

@@ -27,10 +27,12 @@ import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.AmbientContextManager; import android.app.ambientcontext.AmbientContextManager;
import android.app.ambientcontext.IAmbientContextManager; import android.app.ambientcontext.IAmbientContextManager;
import android.app.ambientcontext.IAmbientContextObserver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal;
import android.os.RemoteCallback; import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.ShellCallback; import android.os.ShellCallback;
import android.os.UserHandle; import android.os.UserHandle;
@@ -48,6 +50,7 @@ import com.android.server.pm.KnownPackages;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@@ -67,31 +70,27 @@ public class AmbientContextManagerService extends
static class ClientRequest { static class ClientRequest {
private final int mUserId; private final int mUserId;
private final AmbientContextEventRequest mRequest; private final AmbientContextEventRequest mRequest;
private final PendingIntent mPendingIntent; private final String mPackageName;
private final RemoteCallback mClientStatusCallback; private final IAmbientContextObserver mObserver;
ClientRequest(int userId, AmbientContextEventRequest request, ClientRequest(int userId, AmbientContextEventRequest request,
PendingIntent pendingIntent, RemoteCallback clientStatusCallback) { String packageName, IAmbientContextObserver observer) {
this.mUserId = userId; this.mUserId = userId;
this.mRequest = request; this.mRequest = request;
this.mPendingIntent = pendingIntent; this.mPackageName = packageName;
this.mClientStatusCallback = clientStatusCallback; this.mObserver = observer;
} }
String getPackageName() { String getPackageName() {
return mPendingIntent.getCreatorPackage(); return mPackageName;
} }
AmbientContextEventRequest getRequest() { AmbientContextEventRequest getRequest() {
return mRequest; return mRequest;
} }
PendingIntent getPendingIntent() { IAmbientContextObserver getObserver() {
return mPendingIntent; return mObserver;
}
RemoteCallback getClientStatusCallback() {
return mClientStatusCallback;
} }
boolean hasUserId(int userId) { boolean hasUserId(int userId) {
@@ -139,16 +138,16 @@ public class AmbientContextManagerService extends
} }
void newClientAdded(int userId, AmbientContextEventRequest request, void newClientAdded(int userId, AmbientContextEventRequest request,
PendingIntent pendingIntent, RemoteCallback clientStatusCallback) { String callingPackage, IAmbientContextObserver observer) {
Slog.d(TAG, "New client added: " + pendingIntent.getCreatorPackage()); Slog.d(TAG, "New client added: " + callingPackage);
// Remove any existing ClientRequest for this user and package. // Remove any existing ClientRequest for this user and package.
mExistingClientRequests.removeAll( mExistingClientRequests.removeAll(
findExistingRequests(userId, pendingIntent.getCreatorPackage())); findExistingRequests(userId, callingPackage));
// Add to existing ClientRequests // Add to existing ClientRequests
mExistingClientRequests.add( mExistingClientRequests.add(
new ClientRequest(userId, request, pendingIntent, clientStatusCallback)); new ClientRequest(userId, request, callingPackage, observer));
} }
void clientRemoved(int userId, String packageName) { void clientRemoved(int userId, String packageName) {
@@ -167,10 +166,10 @@ public class AmbientContextManagerService extends
} }
@Nullable @Nullable
PendingIntent getPendingIntent(int userId, String packageName) { IAmbientContextObserver getClientRequestObserver(int userId, String packageName) {
for (ClientRequest clientRequest : mExistingClientRequests) { for (ClientRequest clientRequest : mExistingClientRequests) {
if (clientRequest.hasUserIdAndPackageName(userId, packageName)) { if (clientRequest.hasUserIdAndPackageName(userId, packageName)) {
return clientRequest.getPendingIntent(); return clientRequest.getObserver();
} }
} }
return null; return null;
@@ -236,15 +235,13 @@ public class AmbientContextManagerService extends
* Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
*/ */
void startDetection(@UserIdInt int userId, AmbientContextEventRequest request, void startDetection(@UserIdInt int userId, AmbientContextEventRequest request,
String packageName, RemoteCallback detectionResultCallback, String packageName, IAmbientContextObserver observer) {
RemoteCallback statusCallback) {
mContext.enforceCallingOrSelfPermission( mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
synchronized (mLock) { synchronized (mLock) {
final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
if (service != null) { if (service != null) {
service.startDetection(request, packageName, detectionResultCallback, service.startDetection(request, packageName, observer);
statusCallback);
} else { } else {
Slog.i(TAG, "service not available for user_id: " + userId); Slog.i(TAG, "service not available for user_id: " + userId);
} }
@@ -297,8 +294,7 @@ public class AmbientContextManagerService extends
Slog.d(TAG, "Restoring detection for " + clientRequest.getPackageName()); Slog.d(TAG, "Restoring detection for " + clientRequest.getPackageName());
service.startDetection(clientRequest.getRequest(), service.startDetection(clientRequest.getRequest(),
clientRequest.getPackageName(), clientRequest.getPackageName(),
service.createDetectionResultRemoteCallback(), clientRequest.getObserver());
clientRequest.getClientStatusCallback());
} }
} }
} }
@@ -328,16 +324,45 @@ public class AmbientContextManagerService extends
Objects.requireNonNull(request); Objects.requireNonNull(request);
Objects.requireNonNull(resultPendingIntent); Objects.requireNonNull(resultPendingIntent);
Objects.requireNonNull(statusCallback); Objects.requireNonNull(statusCallback);
// Wrap the PendingIntent and statusCallback in a IAmbientContextObserver to make the
// code unified
IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
@Override
public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
mService.sendDetectionResultIntent(resultPendingIntent, events);
}
@Override
public void onRegistrationComplete(int statusCode) throws RemoteException {
AmbientContextManagerPerUserService.sendStatusCallback(statusCallback,
statusCode);
}
};
registerObserverWithCallback(request, resultPendingIntent.getCreatorPackage(),
observer);
}
/**
* Register an observer for Ambient Context events.
*/
@Override
public void registerObserverWithCallback(AmbientContextEventRequest request,
String packageName,
IAmbientContextObserver observer) {
Slog.i(TAG, "AmbientContextManagerService registerObserverWithCallback.");
Objects.requireNonNull(request);
Objects.requireNonNull(packageName);
Objects.requireNonNull(observer);
mContext.enforceCallingOrSelfPermission( mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
assertCalledByPackageOwner(resultPendingIntent.getCreatorPackage()); assertCalledByPackageOwner(packageName);
if (!mIsServiceEnabled) { if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available."); Slog.w(TAG, "Service not available.");
mService.sendStatusCallback(statusCallback, AmbientContextManagerPerUserService.completeRegistration(observer,
AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
return; return;
} }
mService.onRegisterObserver(request, resultPendingIntent, statusCallback); mService.onRegisterObserver(request, packageName, observer);
} }
@Override @Override
@@ -359,7 +384,7 @@ public class AmbientContextManagerService extends
assertCalledByPackageOwner(callingPackage); assertCalledByPackageOwner(callingPackage);
if (!mIsServiceEnabled) { if (!mIsServiceEnabled) {
Slog.w(TAG, "Detection service not available."); Slog.w(TAG, "Detection service not available.");
mService.sendStatusToCallback(statusCallback, AmbientContextManagerPerUserService.sendStatusCallback(statusCallback,
AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
return; return;
} }

View File

@@ -22,13 +22,15 @@ import android.annotation.NonNull;
import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.AmbientContextManager; import android.app.ambientcontext.AmbientContextManager;
import android.app.ambientcontext.IAmbientContextObserver;
import android.content.ComponentName; import android.content.ComponentName;
import android.os.Binder; import android.os.Binder;
import android.os.RemoteCallback; import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ShellCommand; import android.os.ShellCommand;
import android.service.ambientcontext.AmbientContextDetectionResult;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.List;
/** /**
* Shell command for {@link AmbientContextManagerService}. * Shell command for {@link AmbientContextManagerService}.
@@ -39,6 +41,7 @@ final class AmbientContextShellCommand extends ShellCommand {
new AmbientContextEventRequest.Builder() new AmbientContextEventRequest.Builder()
.addEventType(AmbientContextEvent.EVENT_COUGH) .addEventType(AmbientContextEvent.EVENT_COUGH)
.addEventType(AmbientContextEvent.EVENT_SNORE) .addEventType(AmbientContextEvent.EVENT_SNORE)
.addEventType(AmbientContextEvent.EVENT_BACK_DOUBLE_TAP)
.build(); .build();
@NonNull @NonNull
@@ -50,11 +53,11 @@ final class AmbientContextShellCommand extends ShellCommand {
/** Callbacks for AmbientContextEventService results used internally for testing. */ /** Callbacks for AmbientContextEventService results used internally for testing. */
static class TestableCallbackInternal { static class TestableCallbackInternal {
private AmbientContextDetectionResult mLastResult; private List<AmbientContextEvent> mLastEvents;
private int mLastStatus; private int mLastStatus;
public AmbientContextDetectionResult getLastResult() { public List<AmbientContextEvent> getLastEvents() {
return mLastResult; return mLastEvents;
} }
public int getLastStatus() { public int getLastStatus() {
@@ -62,19 +65,19 @@ final class AmbientContextShellCommand extends ShellCommand {
} }
@NonNull @NonNull
private RemoteCallback createRemoteDetectionResultCallback() { private IAmbientContextObserver createAmbientContextObserver() {
return new RemoteCallback(result -> { return new IAmbientContextObserver.Stub() {
AmbientContextDetectionResult detectionResult = @Override
(AmbientContextDetectionResult) result.get( public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY); mLastEvents = events;
final long token = Binder.clearCallingIdentity(); out.println("Detection events available: " + events);
try {
mLastResult = detectionResult;
out.println("Detection result available: " + detectionResult);
} finally {
Binder.restoreCallingIdentity(token);
} }
});
@Override
public void onRegistrationComplete(int statusCode) throws RemoteException {
mLastStatus = statusCode;
}
};
} }
@NonNull @NonNull
@@ -123,8 +126,7 @@ final class AmbientContextShellCommand extends ShellCommand {
final String packageName = getNextArgRequired(); final String packageName = getNextArgRequired();
mService.startDetection( mService.startDetection(
userId, REQUEST, packageName, userId, REQUEST, packageName,
sTestableCallbackInternal.createRemoteDetectionResultCallback(), sTestableCallbackInternal.createAmbientContextObserver());
sTestableCallbackInternal.createRemoteStatusCallback());
return 0; return 0;
} }

View File

@@ -21,8 +21,8 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.IAmbientContextObserver;
import android.content.Intent; import android.content.Intent;
import android.os.RemoteCallback;
import android.os.UserHandle; import android.os.UserHandle;
import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry;
@@ -30,6 +30,8 @@ import androidx.test.filters.SmallTest;
import org.junit.Test; import org.junit.Test;
import java.util.List;
/** /**
* Unit test for {@link AmbientContextManagerService}. * Unit test for {@link AmbientContextManagerService}.
* atest FrameworksServicesTests:AmbientContextManagerServiceTest * atest FrameworksServicesTests:AmbientContextManagerServiceTest
@@ -48,12 +50,22 @@ public class AmbientContextManagerServiceTest {
PendingIntent pendingIntent = PendingIntent.getBroadcast( PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstrumentationRegistry.getTargetContext(), 0, InstrumentationRegistry.getTargetContext(), 0,
intent, PendingIntent.FLAG_IMMUTABLE); intent, PendingIntent.FLAG_IMMUTABLE);
IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
@Override
public void onEvents(List<AmbientContextEvent> events) {
}
@Override
public void onRegistrationComplete(int statusCode) {
}
};
AmbientContextManagerService.ClientRequest clientRequest = AmbientContextManagerService.ClientRequest clientRequest =
new AmbientContextManagerService.ClientRequest(USER_ID, request, new AmbientContextManagerService.ClientRequest(USER_ID, request,
pendingIntent, new RemoteCallback(result -> {})); pendingIntent.getCreatorPackage(), observer);
assertThat(clientRequest.getRequest()).isEqualTo(request); assertThat(clientRequest.getRequest()).isEqualTo(request);
assertThat(clientRequest.getPackageName()).isEqualTo(SYSTEM_PACKAGE_NAME); assertThat(clientRequest.getPackageName()).isEqualTo(SYSTEM_PACKAGE_NAME);
assertThat(clientRequest.getObserver()).isEqualTo(observer);
assertThat(clientRequest.hasUserId(USER_ID)).isTrue(); assertThat(clientRequest.hasUserId(USER_ID)).isTrue();
assertThat(clientRequest.hasUserId(-1)).isFalse(); assertThat(clientRequest.hasUserId(-1)).isFalse();
assertThat(clientRequest.hasUserIdAndPackageName(USER_ID, SYSTEM_PACKAGE_NAME)).isTrue(); assertThat(clientRequest.hasUserIdAndPackageName(USER_ID, SYSTEM_PACKAGE_NAME)).isTrue();