Merge "Add query API for apps to check the event status. The detection service sends back the response with a status. Add client API for apps to start the consent activity."
This commit is contained in:
@@ -1359,14 +1359,13 @@ package android.app.ambientcontext {
|
||||
method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder setOptions(@NonNull android.os.PersistableBundle);
|
||||
}
|
||||
|
||||
public final class AmbientContextEventResponse implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @Nullable public android.app.PendingIntent getActionPendingIntent();
|
||||
method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents();
|
||||
method @NonNull public String getPackageName();
|
||||
method public int getStatusCode();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventResponse> CREATOR;
|
||||
public final class AmbientContextManager {
|
||||
method @NonNull public static java.util.List<android.app.ambientcontext.AmbientContextEvent> getEventsFromIntent(@NonNull android.content.Intent);
|
||||
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void queryAmbientContextServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
|
||||
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
|
||||
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void startConsentActivity(@NonNull java.util.Set<java.lang.Integer>);
|
||||
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver();
|
||||
field public static final String EXTRA_AMBIENT_CONTEXT_EVENTS = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS";
|
||||
field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
|
||||
field public static final int STATUS_MICROPHONE_DISABLED = 4; // 0x4
|
||||
field public static final int STATUS_NOT_SUPPORTED = 2; // 0x2
|
||||
@@ -1375,22 +1374,6 @@ package android.app.ambientcontext {
|
||||
field public static final int STATUS_UNKNOWN = 0; // 0x0
|
||||
}
|
||||
|
||||
public static final class AmbientContextEventResponse.Builder {
|
||||
ctor public AmbientContextEventResponse.Builder();
|
||||
method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent);
|
||||
method @NonNull public android.app.ambientcontext.AmbientContextEventResponse build();
|
||||
method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setActionPendingIntent(@NonNull android.app.PendingIntent);
|
||||
method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setPackageName(@NonNull String);
|
||||
method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setStatusCode(int);
|
||||
}
|
||||
|
||||
public final class AmbientContextManager {
|
||||
method @Nullable public static android.app.ambientcontext.AmbientContextEventResponse getResponseFromIntent(@NonNull android.content.Intent);
|
||||
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent);
|
||||
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver();
|
||||
field public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.app.assist {
|
||||
@@ -10704,14 +10687,45 @@ package android.security.keystore.recovery {
|
||||
|
||||
package android.service.ambientcontext {
|
||||
|
||||
public final class AmbientContextDetectionResult implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents();
|
||||
method @NonNull public String getPackageName();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.service.ambientcontext.AmbientContextDetectionResult> CREATOR;
|
||||
}
|
||||
|
||||
public static final class AmbientContextDetectionResult.Builder {
|
||||
ctor public AmbientContextDetectionResult.Builder();
|
||||
method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent);
|
||||
method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult build();
|
||||
method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder setPackageName(@NonNull String);
|
||||
}
|
||||
|
||||
public abstract class AmbientContextDetectionService extends android.app.Service {
|
||||
ctor public AmbientContextDetectionService();
|
||||
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
|
||||
method public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.app.ambientcontext.AmbientContextEventResponse>);
|
||||
method @BinderThread public abstract void onQueryServiceStatus(@NonNull int[], @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
|
||||
method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
|
||||
method public abstract void onStopDetection(@NonNull String);
|
||||
field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService";
|
||||
}
|
||||
|
||||
public final class AmbientContextDetectionServiceStatus implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @NonNull public String getPackageName();
|
||||
method public int getStatusCode();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.service.ambientcontext.AmbientContextDetectionServiceStatus> CREATOR;
|
||||
}
|
||||
|
||||
public static final class AmbientContextDetectionServiceStatus.Builder {
|
||||
ctor public AmbientContextDetectionServiceStatus.Builder();
|
||||
method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus build();
|
||||
method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setPackageName(@NonNull String);
|
||||
method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setStatusCode(int);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.service.appprediction {
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.app.ContextImpl.ServiceInitializationState;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.IDevicePolicyManager;
|
||||
import android.app.ambientcontext.AmbientContextManager;
|
||||
import android.app.ambientcontext.IAmbientContextEventObserver;
|
||||
import android.app.ambientcontext.IAmbientContextManager;
|
||||
import android.app.appsearch.AppSearchManagerFrameworkInitializer;
|
||||
import android.app.blob.BlobStoreManagerFrameworkInitializer;
|
||||
import android.app.cloudsearch.CloudSearchManager;
|
||||
@@ -1542,8 +1542,8 @@ public final class SystemServiceRegistry {
|
||||
throws ServiceNotFoundException {
|
||||
IBinder iBinder = ServiceManager.getServiceOrThrow(
|
||||
Context.AMBIENT_CONTEXT_SERVICE);
|
||||
IAmbientContextEventObserver manager =
|
||||
IAmbientContextEventObserver.Stub.asInterface(iBinder);
|
||||
IAmbientContextManager manager =
|
||||
IAmbientContextManager.Stub.asInterface(iBinder);
|
||||
return new AmbientContextManager(ctx.getOuterContext(), manager);
|
||||
}});
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ import android.os.Parcelable;
|
||||
import android.os.PersistableBundle;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.internal.util.AnnotationValidations;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -36,15 +39,17 @@ public final class AmbientContextEventRequest implements Parcelable {
|
||||
@NonNull private final Set<Integer> mEventTypes;
|
||||
@NonNull private final PersistableBundle mOptions;
|
||||
|
||||
AmbientContextEventRequest(
|
||||
private AmbientContextEventRequest(
|
||||
@NonNull Set<Integer> eventTypes,
|
||||
@NonNull PersistableBundle options) {
|
||||
this.mEventTypes = eventTypes;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mEventTypes);
|
||||
AnnotationValidations.validate(NonNull.class, null, mEventTypes);
|
||||
Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty");
|
||||
for (int eventType : eventTypes) {
|
||||
AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType);
|
||||
}
|
||||
this.mOptions = options;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mOptions);
|
||||
AnnotationValidations.validate(NonNull.class, null, mOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,16 +85,20 @@ public final class AmbientContextEventRequest implements Parcelable {
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
AmbientContextEventRequest(@NonNull Parcel in) {
|
||||
private AmbientContextEventRequest(@NonNull Parcel in) {
|
||||
Set<Integer> eventTypes = (Set<Integer>) in.readArraySet(Integer.class.getClassLoader());
|
||||
PersistableBundle options = (PersistableBundle) in.readTypedObject(
|
||||
PersistableBundle.CREATOR);
|
||||
|
||||
this.mEventTypes = eventTypes;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
AnnotationValidations.validate(
|
||||
NonNull.class, null, mEventTypes);
|
||||
Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty");
|
||||
for (int eventType : eventTypes) {
|
||||
AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType);
|
||||
}
|
||||
this.mOptions = options;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
AnnotationValidations.validate(
|
||||
NonNull.class, null, mOptions);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,293 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app.ambientcontext;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.AnnotationValidations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a response from the {@code AmbientContextEvent} service.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class AmbientContextEventResponse implements Parcelable {
|
||||
/**
|
||||
* An unknown status.
|
||||
*/
|
||||
public static final int STATUS_UNKNOWN = 0;
|
||||
/**
|
||||
* The value of the status code that indicates success.
|
||||
*/
|
||||
public static final int STATUS_SUCCESS = 1;
|
||||
/**
|
||||
* The value of the status code that indicates one or more of the
|
||||
* requested events are not supported.
|
||||
*/
|
||||
public static final int STATUS_NOT_SUPPORTED = 2;
|
||||
/**
|
||||
* The value of the status code that indicates service not available.
|
||||
*/
|
||||
public static final int STATUS_SERVICE_UNAVAILABLE = 3;
|
||||
/**
|
||||
* The value of the status code that microphone is disabled.
|
||||
*/
|
||||
public static final int STATUS_MICROPHONE_DISABLED = 4;
|
||||
/**
|
||||
* The value of the status code that the app is not granted access.
|
||||
*/
|
||||
public static final int STATUS_ACCESS_DENIED = 5;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "STATUS_" }, value = {
|
||||
STATUS_UNKNOWN,
|
||||
STATUS_SUCCESS,
|
||||
STATUS_NOT_SUPPORTED,
|
||||
STATUS_SERVICE_UNAVAILABLE,
|
||||
STATUS_MICROPHONE_DISABLED,
|
||||
STATUS_ACCESS_DENIED
|
||||
}) public @interface StatusCode {}
|
||||
|
||||
@StatusCode private final int mStatusCode;
|
||||
@NonNull private final List<AmbientContextEvent> mEvents;
|
||||
@NonNull private final String mPackageName;
|
||||
@Nullable private final PendingIntent mActionPendingIntent;
|
||||
|
||||
/** @hide */
|
||||
public static String statusToString(@StatusCode int value) {
|
||||
switch (value) {
|
||||
case STATUS_UNKNOWN:
|
||||
return "STATUS_UNKNOWN";
|
||||
case STATUS_SUCCESS:
|
||||
return "STATUS_SUCCESS";
|
||||
case STATUS_NOT_SUPPORTED:
|
||||
return "STATUS_NOT_SUPPORTED";
|
||||
case STATUS_SERVICE_UNAVAILABLE:
|
||||
return "STATUS_SERVICE_UNAVAILABLE";
|
||||
case STATUS_MICROPHONE_DISABLED:
|
||||
return "STATUS_MICROPHONE_DISABLED";
|
||||
case STATUS_ACCESS_DENIED:
|
||||
return "STATUS_ACCESS_DENIED";
|
||||
default: return Integer.toHexString(value);
|
||||
}
|
||||
}
|
||||
|
||||
AmbientContextEventResponse(
|
||||
@StatusCode int statusCode,
|
||||
@NonNull List<AmbientContextEvent> events,
|
||||
@NonNull String packageName,
|
||||
@Nullable PendingIntent actionPendingIntent) {
|
||||
this.mStatusCode = statusCode;
|
||||
AnnotationValidations.validate(StatusCode.class, null, mStatusCode);
|
||||
this.mEvents = events;
|
||||
AnnotationValidations.validate(NonNull.class, null, mEvents);
|
||||
this.mPackageName = packageName;
|
||||
AnnotationValidations.validate(NonNull.class, null, mPackageName);
|
||||
this.mActionPendingIntent = actionPendingIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of the response.
|
||||
*/
|
||||
public @StatusCode int getStatusCode() {
|
||||
return mStatusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The detected event.
|
||||
*/
|
||||
public @NonNull List<AmbientContextEvent> getEvents() {
|
||||
return mEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package to deliver the response to.
|
||||
*/
|
||||
public @NonNull String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link PendingIntent} that the client should call to allow further actions by user.
|
||||
* For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to the
|
||||
* grant access activity.
|
||||
*/
|
||||
public @Nullable PendingIntent getActionPendingIntent() {
|
||||
return mActionPendingIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AmbientContextEventResponse { " + "statusCode = " + mStatusCode + ", "
|
||||
+ "events = " + mEvents + ", " + "packageName = " + mPackageName + ", "
|
||||
+ "callbackPendingIntent = " + mActionPendingIntent + " }";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
|
||||
byte flg = 0;
|
||||
if (mActionPendingIntent != null) flg |= 0x8;
|
||||
dest.writeByte(flg);
|
||||
dest.writeInt(mStatusCode);
|
||||
dest.writeParcelableList(mEvents, flags);
|
||||
dest.writeString(mPackageName);
|
||||
if (mActionPendingIntent != null) dest.writeTypedObject(mActionPendingIntent, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
AmbientContextEventResponse(@NonNull android.os.Parcel in) {
|
||||
byte flg = in.readByte();
|
||||
int statusCode = in.readInt();
|
||||
List<AmbientContextEvent> events = new ArrayList<>();
|
||||
in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(),
|
||||
AmbientContextEvent.class);
|
||||
String packageName = in.readString();
|
||||
PendingIntent callbackPendingIntent = (flg & 0x8) == 0 ? null
|
||||
: (PendingIntent) in.readTypedObject(PendingIntent.CREATOR);
|
||||
|
||||
this.mStatusCode = statusCode;
|
||||
AnnotationValidations.validate(
|
||||
StatusCode.class, null, mStatusCode);
|
||||
this.mEvents = events;
|
||||
AnnotationValidations.validate(
|
||||
NonNull.class, null, mEvents);
|
||||
this.mPackageName = packageName;
|
||||
AnnotationValidations.validate(
|
||||
NonNull.class, null, mPackageName);
|
||||
this.mActionPendingIntent = callbackPendingIntent;
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<AmbientContextEventResponse> CREATOR =
|
||||
new Parcelable.Creator<AmbientContextEventResponse>() {
|
||||
@Override
|
||||
public AmbientContextEventResponse[] newArray(int size) {
|
||||
return new AmbientContextEventResponse[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmbientContextEventResponse createFromParcel(@NonNull android.os.Parcel in) {
|
||||
return new AmbientContextEventResponse(in);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A builder for {@link AmbientContextEventResponse}
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static final class Builder {
|
||||
private @StatusCode int mStatusCode;
|
||||
private @NonNull List<AmbientContextEvent> mEvents;
|
||||
private @NonNull String mPackageName;
|
||||
private @Nullable PendingIntent mCallbackPendingIntent;
|
||||
private long mBuilderFieldsSet = 0L;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of the response.
|
||||
*/
|
||||
public @NonNull Builder setStatusCode(@StatusCode int value) {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x1;
|
||||
mStatusCode = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event to the builder.
|
||||
*/
|
||||
public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) {
|
||||
checkNotUsed();
|
||||
if (mEvents == null) {
|
||||
mBuilderFieldsSet |= 0x2;
|
||||
mEvents = new ArrayList<>();
|
||||
}
|
||||
mEvents.add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package to deliver the response to.
|
||||
*/
|
||||
public @NonNull Builder setPackageName(@NonNull String value) {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x4;
|
||||
mPackageName = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link PendingIntent} that the client should call to allow further actions by user.
|
||||
* For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to
|
||||
* the grant access activity.
|
||||
*/
|
||||
public @NonNull Builder setActionPendingIntent(@NonNull PendingIntent value) {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x8;
|
||||
mCallbackPendingIntent = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds the instance. This builder should not be touched after calling this! */
|
||||
public @NonNull AmbientContextEventResponse build() {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x10; // Mark builder used
|
||||
|
||||
if ((mBuilderFieldsSet & 0x1) == 0) {
|
||||
mStatusCode = STATUS_UNKNOWN;
|
||||
}
|
||||
if ((mBuilderFieldsSet & 0x2) == 0) {
|
||||
mEvents = new ArrayList<>();
|
||||
}
|
||||
if ((mBuilderFieldsSet & 0x4) == 0) {
|
||||
mPackageName = "";
|
||||
}
|
||||
if ((mBuilderFieldsSet & 0x8) == 0) {
|
||||
mCallbackPendingIntent = null;
|
||||
}
|
||||
AmbientContextEventResponse o = new AmbientContextEventResponse(
|
||||
mStatusCode,
|
||||
mEvents,
|
||||
mPackageName,
|
||||
mCallbackPendingIntent);
|
||||
return o;
|
||||
}
|
||||
|
||||
private void checkNotUsed() {
|
||||
if ((mBuilderFieldsSet & 0x10) != 0) {
|
||||
throw new IllegalStateException(
|
||||
"This Builder should not be reused. Use a new Builder instance instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,116 +17,280 @@
|
||||
package android.app.ambientcontext;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Allows granted apps to register for particular pre-defined {@link AmbientContextEvent}s.
|
||||
* After successful registration, the app receives a callback on the provided {@link PendingIntent}
|
||||
* when the requested event is detected.
|
||||
* <p />
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre><code>
|
||||
* // Create request
|
||||
* AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
|
||||
* .addEventType(AmbientContextEvent.EVENT_COUGH)
|
||||
* .addEventTYpe(AmbientContextEvent.EVENT_SNORE)
|
||||
* .build();
|
||||
* // Create PendingIntent
|
||||
* Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class)
|
||||
* .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
* PendingIntent pendingIntent = PendingIntents.getBroadcastMutable(context, 0, intent, 0);
|
||||
* // Register for events
|
||||
* AmbientContextManager ambientContextManager =
|
||||
* context.getSystemService(AmbientContextManager.class);
|
||||
* ambientContextManager.registerObserver(request, pendingIntent);
|
||||
*
|
||||
* // Handle the callback intent in your receiver
|
||||
* {@literal @}Override
|
||||
* protected void onReceive(Context context, Intent intent) {
|
||||
* AmbientContextEventResponse response =
|
||||
* AmbientContextManager.getResponseFromIntent(intent);
|
||||
* if (response != null) {
|
||||
* if (response.getStatusCode() == AmbientContextEventResponse.STATUS_SUCCESS) {
|
||||
* // Do something useful with response.getEvent()
|
||||
* } else if (response.getStatusCode() == AmbientContextEventResponse.STATUS_ACCESS_DENIED) {
|
||||
* // Redirect users to grant access
|
||||
* PendingIntent callbackPendingIntent = response.getCallbackPendingIntent();
|
||||
* if (callbackPendingIntent != null) {
|
||||
* callbackPendingIntent.send();
|
||||
* }
|
||||
* } else ...
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* Allows granted apps to register for event types defined in {@link AmbientContextEvent}.
|
||||
* After registration, the app receives a Consumer callback of the service status.
|
||||
* If it is {@link STATUS_SUCCESSFUL}, when the requested events are detected, the provided
|
||||
* {@link PendingIntent} callback will receive the list of detected {@link AmbientContextEvent}s.
|
||||
* If it is {@link STATUS_ACCESS_DENIED}, the app can call {@link #startConsentActivity}
|
||||
* to load the consent screen.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@SystemService(Context.AMBIENT_CONTEXT_SERVICE)
|
||||
public final class AmbientContextManager {
|
||||
|
||||
/**
|
||||
* The key of an Intent extra indicating the response.
|
||||
* The bundle key for the service status query result, used in
|
||||
* {@code RemoteCallback#sendResult}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE =
|
||||
"android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE";
|
||||
public static final String STATUS_RESPONSE_BUNDLE_KEY =
|
||||
"android.app.ambientcontext.AmbientContextStatusBundleKey";
|
||||
|
||||
/**
|
||||
* Allows clients to retrieve the response from the intent.
|
||||
* The key of an intent extra indicating a list of detected {@link AmbientContextEvent}s.
|
||||
* The intent is sent to the app in the app's registered {@link PendingIntent}.
|
||||
*/
|
||||
public static final String EXTRA_AMBIENT_CONTEXT_EVENTS =
|
||||
"android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS";
|
||||
|
||||
/**
|
||||
* An unknown status.
|
||||
*/
|
||||
public static final int STATUS_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* The value of the status code that indicates success.
|
||||
*/
|
||||
public static final int STATUS_SUCCESS = 1;
|
||||
|
||||
/**
|
||||
* The value of the status code that indicates one or more of the
|
||||
* requested events are not supported.
|
||||
*/
|
||||
public static final int STATUS_NOT_SUPPORTED = 2;
|
||||
|
||||
/**
|
||||
* The value of the status code that indicates service not available.
|
||||
*/
|
||||
public static final int STATUS_SERVICE_UNAVAILABLE = 3;
|
||||
|
||||
/**
|
||||
* The value of the status code that microphone is disabled.
|
||||
*/
|
||||
public static final int STATUS_MICROPHONE_DISABLED = 4;
|
||||
|
||||
/**
|
||||
* The value of the status code that the app is not granted access.
|
||||
*/
|
||||
public static final int STATUS_ACCESS_DENIED = 5;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "STATUS_" }, value = {
|
||||
STATUS_UNKNOWN,
|
||||
STATUS_SUCCESS,
|
||||
STATUS_NOT_SUPPORTED,
|
||||
STATUS_SERVICE_UNAVAILABLE,
|
||||
STATUS_MICROPHONE_DISABLED,
|
||||
STATUS_ACCESS_DENIED
|
||||
}) public @interface StatusCode {}
|
||||
|
||||
/**
|
||||
* Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent.
|
||||
*
|
||||
* @param intent received from the PendingIntent callback
|
||||
*
|
||||
* @return the AmbientContextEventResponse, or null if not present
|
||||
* @return the list of events, or an empty list if the intent doesn't have such events.
|
||||
*/
|
||||
@Nullable
|
||||
public static AmbientContextEventResponse getResponseFromIntent(
|
||||
@NonNull Intent intent) {
|
||||
if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE)) {
|
||||
return intent.getParcelableExtra(EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE);
|
||||
@NonNull public static List<AmbientContextEvent> getEventsFromIntent(@NonNull Intent intent) {
|
||||
if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS)) {
|
||||
return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS);
|
||||
} else {
|
||||
return null;
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final IAmbientContextEventObserver mService;
|
||||
private final IAmbientContextManager mService;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
public AmbientContextManager(Context context, IAmbientContextEventObserver service) {
|
||||
public AmbientContextManager(Context context, IAmbientContextManager service) {
|
||||
mContext = context;
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the {@link AmbientContextEvent} service status for the calling package, and
|
||||
* sends the result to the {@link Consumer} right after the call. This is used by foreground
|
||||
* apps to check whether the requested events are enabled for detection on the device.
|
||||
* If all events are enabled for detection, the response has
|
||||
* {@link AmbientContextManager#STATUS_SUCCESS}.
|
||||
* If any of the events are not consented by user, the response has
|
||||
* {@link AmbientContextManager#STATUS_ACCESS_DENIED}, and the app can
|
||||
* call {@link #startConsentActivity} to redirect the user to the consent screen.
|
||||
* <p />
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre><code>
|
||||
* Set<Integer> eventTypes = new HashSet<>();
|
||||
* eventTypes.add(AmbientContextEvent.EVENT_COUGH);
|
||||
* eventTypes.add(AmbientContextEvent.EVENT_SNORE);
|
||||
*
|
||||
* // Create Consumer
|
||||
* Consumer<Integer> statusConsumer = response -> {
|
||||
* int status = status.getStatusCode();
|
||||
* if (status == AmbientContextManager.STATUS_SUCCESS) {
|
||||
* // Show user it's enabled
|
||||
* } else if (status == AmbientContextManager.STATUS_ACCESS_DENIED) {
|
||||
* // Send user to grant access
|
||||
* startConsentActivity(eventTypes);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // Query status
|
||||
* AmbientContextManager ambientContextManager =
|
||||
* context.getSystemService(AmbientContextManager.class);
|
||||
* ambientContextManager.queryAmbientContextStatus(eventTypes, executor, statusConsumer);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param eventTypes The set of event codes to check status on.
|
||||
* @param executor Executor on which to run the consumer callback.
|
||||
* @param consumer The consumer that handles the status code.
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
|
||||
public void queryAmbientContextServiceStatus(
|
||||
@NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull @StatusCode Consumer<Integer> consumer) {
|
||||
try {
|
||||
RemoteCallback callback = new RemoteCallback(result -> {
|
||||
int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
executor.execute(() -> consumer.accept(status));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
});
|
||||
mService.queryServiceStatus(integerSetToIntArray(eventTypes),
|
||||
mContext.getOpPackageName(), callback);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the consent data host to open an activity that allows users to modify consent.
|
||||
*
|
||||
* @param eventTypes The set of event codes to be consented.
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
|
||||
public void startConsentActivity(
|
||||
@NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes) {
|
||||
try {
|
||||
mService.startConsentActivity(
|
||||
integerSetToIntArray(eventTypes), mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static int[] integerSetToIntArray(@NonNull Set<Integer> integerSet) {
|
||||
int[] intArray = new int[integerSet.size()];
|
||||
int i = 0;
|
||||
for (Integer type : integerSet) {
|
||||
intArray[i++] = type;
|
||||
}
|
||||
return intArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows app to register as a {@link AmbientContextEvent} observer. The
|
||||
* observer receives a callback on the provided {@link PendingIntent} when the requested
|
||||
* event is detected. Registering another observer from the same package that has already been
|
||||
* registered will override the previous observer.
|
||||
* <p />
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre><code>
|
||||
* // Create request
|
||||
* AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
|
||||
* .addEventType(AmbientContextEvent.EVENT_COUGH)
|
||||
* .addEventType(AmbientContextEvent.EVENT_SNORE)
|
||||
* .build();
|
||||
*
|
||||
* // Create PendingIntent for delivering detection results to my receiver
|
||||
* Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class)
|
||||
* .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
* PendingIntent pendingIntent =
|
||||
* PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
*
|
||||
* // Create Consumer of service status
|
||||
* Consumer<Integer> statusConsumer = status -> {
|
||||
* if (status == AmbientContextManager.STATUS_ACCESS_DENIED) {
|
||||
* // User did not consent event detection. See #queryAmbientContextServiceStatus and
|
||||
* // #startConsentActivity
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // Register as observer
|
||||
* AmbientContextManager ambientContextManager =
|
||||
* context.getSystemService(AmbientContextManager.class);
|
||||
* ambientContextManager.registerObserver(request, pendingIntent, executor, statusConsumer);
|
||||
*
|
||||
* // Handle the list of {@link AmbientContextEvent}s in your receiver
|
||||
* {@literal @}Override
|
||||
* protected void onReceive(Context context, Intent intent) {
|
||||
* List<AmbientContextEvent> events = AmbientContextManager.getEventsFromIntent(intent);
|
||||
* if (!events.isEmpty()) {
|
||||
* // Do something useful with the events.
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @param request The request with events to observe.
|
||||
* @param pendingIntent A mutable {@link PendingIntent} that will be dispatched when any
|
||||
* requested event is detected.
|
||||
* @param resultPendingIntent A mutable {@link PendingIntent} that will be dispatched after the
|
||||
* requested events are detected.
|
||||
* @param executor Executor on which to run the consumer callback.
|
||||
* @param statusConsumer A consumer that handles the status code, which is returned
|
||||
* right after the call.
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
|
||||
public void registerObserver(
|
||||
@NonNull AmbientContextEventRequest request,
|
||||
@NonNull PendingIntent pendingIntent) {
|
||||
Preconditions.checkArgument(!pendingIntent.isImmutable());
|
||||
@NonNull PendingIntent resultPendingIntent,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
||||
Preconditions.checkArgument(!resultPendingIntent.isImmutable());
|
||||
try {
|
||||
mService.registerObserver(request, pendingIntent);
|
||||
RemoteCallback callback = new RemoteCallback(result -> {
|
||||
int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
executor.execute(() -> statusConsumer.accept(statusCode));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
});
|
||||
mService.registerObserver(request, resultPendingIntent, callback);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
@@ -18,13 +18,19 @@ package android.app.ambientcontext;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ambientcontext.AmbientContextEventRequest;
|
||||
import android.os.RemoteCallback;
|
||||
|
||||
/**
|
||||
* Interface for an AmbientContextEventManager that provides access to AmbientContextEvents.
|
||||
* Interface for an AmbientContextManager that provides access to AmbientContextEvents.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IAmbientContextEventObserver {
|
||||
void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent);
|
||||
oneway interface IAmbientContextManager {
|
||||
void registerObserver(in AmbientContextEventRequest request,
|
||||
in PendingIntent resultPendingIntent,
|
||||
in RemoteCallback statusCallback);
|
||||
void unregisterObserver(in String callingPackage);
|
||||
void queryServiceStatus(in int[] eventTypes, in String callingPackage,
|
||||
in RemoteCallback statusCallback);
|
||||
void startConsentActivity(in int[] eventTypes, in String callingPackage);
|
||||
}
|
||||
@@ -10320,6 +10320,34 @@ public final class Settings {
|
||||
public static final String NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT =
|
||||
"nearby_fast_pair_settings_devices_component";
|
||||
|
||||
/**
|
||||
* Current provider of the component for requesting ambient context consent.
|
||||
* Default value in @string/config_defaultAmbientContextConsentComponent.
|
||||
* No VALIDATOR as this setting will not be backed up.
|
||||
* @hide
|
||||
*/
|
||||
public static final String AMBIENT_CONTEXT_CONSENT_COMPONENT =
|
||||
"ambient_context_consent_component";
|
||||
|
||||
/**
|
||||
* Current provider of the intent extra key for the caller's package name while
|
||||
* requesting ambient context consent.
|
||||
* No VALIDATOR as this setting will not be backed up.
|
||||
* @hide
|
||||
*/
|
||||
public static final String AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY =
|
||||
"ambient_context_package_name_key";
|
||||
|
||||
/**
|
||||
* Current provider of the intent extra key for the event code int array while
|
||||
* requesting ambient context consent.
|
||||
* Default value in @string/config_ambientContextEventArrayExtraKey.
|
||||
* No VALIDATOR as this setting will not be backed up.
|
||||
* @hide
|
||||
*/
|
||||
public static final String AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY =
|
||||
"ambient_context_event_array_key";
|
||||
|
||||
/**
|
||||
* Controls whether aware is enabled.
|
||||
* @hide
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app.ambientcontext;
|
||||
package android.service.ambientcontext;
|
||||
|
||||
parcelable AmbientContextEventResponse;
|
||||
parcelable AmbientContextDetectionResult;
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.service.ambientcontext;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.ambientcontext.AmbientContextEvent;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.AnnotationValidations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a {@code AmbientContextEvent} detection result reported by the detection service.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class AmbientContextDetectionResult implements Parcelable {
|
||||
|
||||
/**
|
||||
* The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String RESULT_RESPONSE_BUNDLE_KEY =
|
||||
"android.app.ambientcontext.AmbientContextDetectionResultBundleKey";
|
||||
@NonNull private final List<AmbientContextEvent> mEvents;
|
||||
@NonNull private final String mPackageName;
|
||||
|
||||
AmbientContextDetectionResult(
|
||||
@NonNull List<AmbientContextEvent> events,
|
||||
@NonNull String packageName) {
|
||||
this.mEvents = events;
|
||||
AnnotationValidations.validate(NonNull.class, null, mEvents);
|
||||
this.mPackageName = packageName;
|
||||
AnnotationValidations.validate(NonNull.class, null, mPackageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of detected event.
|
||||
*/
|
||||
@SuppressLint("ConcreteCollection")
|
||||
public @NonNull List<AmbientContextEvent> getEvents() {
|
||||
return mEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package to deliver the response to.
|
||||
*/
|
||||
public @NonNull String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AmbientContextEventResponse { "
|
||||
+ "events = " + mEvents + ", " + "packageName = " + mPackageName + " }";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
|
||||
byte flg = 0;
|
||||
dest.writeByte(flg);
|
||||
dest.writeParcelableList(mEvents, flags);
|
||||
dest.writeString(mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
AmbientContextDetectionResult(@NonNull android.os.Parcel in) {
|
||||
byte flg = in.readByte();
|
||||
ArrayList<AmbientContextEvent> events = new ArrayList<>();
|
||||
in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(),
|
||||
AmbientContextEvent.class);
|
||||
String packageName = in.readString();
|
||||
|
||||
this.mEvents = events;
|
||||
AnnotationValidations.validate(
|
||||
NonNull.class, null, mEvents);
|
||||
this.mPackageName = packageName;
|
||||
AnnotationValidations.validate(
|
||||
NonNull.class, null, mPackageName);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<AmbientContextDetectionResult> CREATOR =
|
||||
new Creator<AmbientContextDetectionResult>() {
|
||||
@Override
|
||||
public AmbientContextDetectionResult[] newArray(int size) {
|
||||
return new AmbientContextDetectionResult[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmbientContextDetectionResult createFromParcel(@NonNull android.os.Parcel in) {
|
||||
return new AmbientContextDetectionResult(in);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A builder for {@link AmbientContextDetectionResult}
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static final class Builder {
|
||||
private @NonNull ArrayList<AmbientContextEvent> mEvents;
|
||||
private @NonNull String mPackageName;
|
||||
private long mBuilderFieldsSet = 0L;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event to the builder.
|
||||
*/
|
||||
public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) {
|
||||
checkNotUsed();
|
||||
if (mEvents == null) {
|
||||
mBuilderFieldsSet |= 0x1;
|
||||
mEvents = new ArrayList<>();
|
||||
}
|
||||
mEvents.add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package to deliver the response to.
|
||||
*/
|
||||
public @NonNull Builder setPackageName(@NonNull String value) {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x2;
|
||||
mPackageName = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds the instance. This builder should not be touched after calling this! */
|
||||
public @NonNull AmbientContextDetectionResult build() {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x4; // Mark builder used
|
||||
|
||||
if ((mBuilderFieldsSet & 0x1) == 0) {
|
||||
mEvents = new ArrayList<>();
|
||||
}
|
||||
if ((mBuilderFieldsSet & 0x2) == 0) {
|
||||
mPackageName = "";
|
||||
}
|
||||
AmbientContextDetectionResult o = new AmbientContextDetectionResult(
|
||||
mEvents,
|
||||
mPackageName);
|
||||
return o;
|
||||
}
|
||||
|
||||
private void checkNotUsed() {
|
||||
if ((mBuilderFieldsSet & 0x4) != 0) {
|
||||
throw new IllegalStateException(
|
||||
"This Builder should not be reused. Use a new Builder instance instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package android.service.ambientcontext;
|
||||
|
||||
import android.annotation.BinderThread;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.Service;
|
||||
import android.app.ambientcontext.AmbientContextEvent;
|
||||
import android.app.ambientcontext.AmbientContextEventRequest;
|
||||
import android.app.ambientcontext.AmbientContextEventResponse;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
@@ -64,15 +64,6 @@ public abstract class AmbientContextDetectionService extends Service {
|
||||
public static final String SERVICE_INTERFACE =
|
||||
"android.service.ambientcontext.AmbientContextDetectionService";
|
||||
|
||||
/**
|
||||
* The key for the bundle the parameter of {@code RemoteCallback#sendResult}. Implementation
|
||||
* should set bundle result with this key.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String RESPONSE_BUNDLE_KEY =
|
||||
"android.service.ambientcontext.EventResponseKey";
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public final IBinder onBind(@NonNull Intent intent) {
|
||||
@@ -82,19 +73,30 @@ public abstract class AmbientContextDetectionService extends Service {
|
||||
@Override
|
||||
public void startDetection(
|
||||
@NonNull AmbientContextEventRequest request, String packageName,
|
||||
RemoteCallback callback) {
|
||||
RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
|
||||
Objects.requireNonNull(request);
|
||||
Objects.requireNonNull(callback);
|
||||
Consumer<AmbientContextEventResponse> consumer =
|
||||
response -> {
|
||||
Objects.requireNonNull(packageName);
|
||||
Objects.requireNonNull(detectionResultCallback);
|
||||
Objects.requireNonNull(statusCallback);
|
||||
Consumer<AmbientContextDetectionResult> detectionResultConsumer =
|
||||
result -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(
|
||||
AmbientContextDetectionService.RESPONSE_BUNDLE_KEY,
|
||||
response);
|
||||
callback.sendResult(bundle);
|
||||
AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY,
|
||||
result);
|
||||
detectionResultCallback.sendResult(bundle);
|
||||
};
|
||||
Consumer<AmbientContextDetectionServiceStatus> statusConsumer =
|
||||
status -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(
|
||||
AmbientContextDetectionServiceStatus
|
||||
.STATUS_RESPONSE_BUNDLE_KEY,
|
||||
status);
|
||||
statusCallback.sendResult(bundle);
|
||||
};
|
||||
AmbientContextDetectionService.this.onStartDetection(
|
||||
request, packageName, consumer);
|
||||
request, packageName, detectionResultConsumer, statusConsumer);
|
||||
Slog.d(TAG, "startDetection " + request);
|
||||
}
|
||||
|
||||
@@ -104,29 +106,52 @@ public abstract class AmbientContextDetectionService extends Service {
|
||||
Objects.requireNonNull(packageName);
|
||||
AmbientContextDetectionService.this.onStopDetection(packageName);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void queryServiceStatus(
|
||||
@AmbientContextEvent.EventCode int[] eventTypes,
|
||||
String packageName,
|
||||
RemoteCallback callback) {
|
||||
Objects.requireNonNull(eventTypes);
|
||||
Objects.requireNonNull(packageName);
|
||||
Objects.requireNonNull(callback);
|
||||
Consumer<AmbientContextDetectionServiceStatus> consumer =
|
||||
response -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(
|
||||
AmbientContextDetectionServiceStatus
|
||||
.STATUS_RESPONSE_BUNDLE_KEY,
|
||||
response);
|
||||
callback.sendResult(bundle);
|
||||
};
|
||||
AmbientContextDetectionService.this.onQueryServiceStatus(
|
||||
eventTypes, packageName, consumer);
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts detection and provides detected events to the consumer. The ongoing detection will
|
||||
* keep running, until onStopDetection is called. If there were previously requested
|
||||
* Starts detection and provides detected events to the statusConsumer. The ongoing detection
|
||||
* will keep running, until onStopDetection is called. If there were previously requested
|
||||
* detection from the same package, the previous request will be replaced with the new request.
|
||||
* The implementation should keep track of whether the user consented each requested
|
||||
* AmbientContextEvent for the app. If not consented, the response should set status
|
||||
* STATUS_ACCESS_DENIED and include an action PendingIntent for the app to redirect the user
|
||||
* to the consent screen.
|
||||
* AmbientContextEvent for the app. If not consented, the statusConsumer should get a response
|
||||
* with STATUS_ACCESS_DENIED.
|
||||
*
|
||||
* @param request The request with events to detect, optional detection window and other
|
||||
* options.
|
||||
* @param request The request with events to detect.
|
||||
* @param packageName the requesting app's package name
|
||||
* @param consumer the consumer for the detected event
|
||||
* @param detectionResultConsumer the consumer for the detected event
|
||||
* @param statusConsumer the consumer for the service status.
|
||||
*/
|
||||
@BinderThread
|
||||
public abstract void onStartDetection(
|
||||
@NonNull AmbientContextEventRequest request,
|
||||
@NonNull String packageName,
|
||||
@NonNull Consumer<AmbientContextEventResponse> consumer);
|
||||
@NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer,
|
||||
@NonNull Consumer<AmbientContextDetectionServiceStatus> statusConsumer);
|
||||
|
||||
/**
|
||||
* Stops detection of the events. Events that are not being detected will be ignored.
|
||||
@@ -134,4 +159,19 @@ public abstract class AmbientContextDetectionService extends Service {
|
||||
* @param packageName stops detection for the given package.
|
||||
*/
|
||||
public abstract void onStopDetection(@NonNull String packageName);
|
||||
|
||||
/**
|
||||
* Called when a query for the detection status occurs. The implementation should check
|
||||
* the detection status of the requested events for the package, and provide results in a
|
||||
* {@link AmbientContextDetectionServiceStatus} for the consumer.
|
||||
*
|
||||
* @param eventTypes The events to check for status.
|
||||
* @param packageName the requesting app's package name
|
||||
* @param consumer the consumer for the query results
|
||||
*/
|
||||
@BinderThread
|
||||
public abstract void onQueryServiceStatus(
|
||||
@NonNull int[] eventTypes,
|
||||
@NonNull String packageName,
|
||||
@NonNull Consumer<AmbientContextDetectionServiceStatus> consumer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.service.ambientcontext;
|
||||
|
||||
parcelable AmbientContextDetectionServiceStatus;
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.service.ambientcontext;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.ambientcontext.AmbientContextManager;
|
||||
import android.app.ambientcontext.AmbientContextManager.StatusCode;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.AnnotationValidations;
|
||||
|
||||
/**
|
||||
* Represents a status for the {@code AmbientContextDetectionService}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class AmbientContextDetectionServiceStatus implements Parcelable {
|
||||
/**
|
||||
* The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String STATUS_RESPONSE_BUNDLE_KEY =
|
||||
"android.app.ambientcontext.AmbientContextServiceStatusBundleKey";
|
||||
|
||||
@StatusCode private final int mStatusCode;
|
||||
@NonNull private final String mPackageName;
|
||||
|
||||
AmbientContextDetectionServiceStatus(
|
||||
@StatusCode int statusCode,
|
||||
@NonNull String packageName) {
|
||||
this.mStatusCode = statusCode;
|
||||
AnnotationValidations.validate(StatusCode.class, null, mStatusCode);
|
||||
this.mPackageName = packageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of the service.
|
||||
*/
|
||||
public @StatusCode int getStatusCode() {
|
||||
return mStatusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package to deliver the response to.
|
||||
*/
|
||||
public @NonNull String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AmbientContextDetectionServiceStatus { " + "statusCode = " + mStatusCode + ", "
|
||||
+ "packageName = " + mPackageName + " }";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
|
||||
byte flg = 0;
|
||||
dest.writeByte(flg);
|
||||
dest.writeInt(mStatusCode);
|
||||
dest.writeString(mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
AmbientContextDetectionServiceStatus(@NonNull android.os.Parcel in) {
|
||||
byte flg = in.readByte();
|
||||
int statusCode = in.readInt();
|
||||
String packageName = in.readString();
|
||||
|
||||
this.mStatusCode = statusCode;
|
||||
AnnotationValidations.validate(
|
||||
StatusCode.class, null, mStatusCode);
|
||||
this.mPackageName = packageName;
|
||||
AnnotationValidations.validate(
|
||||
NonNull.class, null, mPackageName);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<AmbientContextDetectionServiceStatus> CREATOR =
|
||||
new Creator<AmbientContextDetectionServiceStatus>() {
|
||||
@Override
|
||||
public AmbientContextDetectionServiceStatus[] newArray(int size) {
|
||||
return new AmbientContextDetectionServiceStatus[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmbientContextDetectionServiceStatus createFromParcel(
|
||||
@NonNull android.os.Parcel in) {
|
||||
return new AmbientContextDetectionServiceStatus(in);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A builder for {@link AmbientContextDetectionServiceStatus}
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static final class Builder {
|
||||
private @StatusCode int mStatusCode;
|
||||
private @NonNull String mPackageName;
|
||||
private long mBuilderFieldsSet = 0L;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status of the service.
|
||||
*/
|
||||
public @NonNull Builder setStatusCode(@StatusCode int value) {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x1;
|
||||
mStatusCode = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package to deliver the response to.
|
||||
*/
|
||||
public @NonNull Builder setPackageName(@NonNull String value) {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x2;
|
||||
mPackageName = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds the instance. This builder should not be touched after calling this! */
|
||||
public @NonNull AmbientContextDetectionServiceStatus build() {
|
||||
checkNotUsed();
|
||||
mBuilderFieldsSet |= 0x4; // Mark builder used
|
||||
|
||||
if ((mBuilderFieldsSet & 0x1) == 0) {
|
||||
mStatusCode = AmbientContextManager.STATUS_UNKNOWN;
|
||||
}
|
||||
if ((mBuilderFieldsSet & 0x2) == 0) {
|
||||
mPackageName = "";
|
||||
}
|
||||
AmbientContextDetectionServiceStatus o = new AmbientContextDetectionServiceStatus(
|
||||
mStatusCode,
|
||||
mPackageName);
|
||||
return o;
|
||||
}
|
||||
|
||||
private void checkNotUsed() {
|
||||
if ((mBuilderFieldsSet & 0x4) != 0) {
|
||||
throw new IllegalStateException(
|
||||
"This Builder should not be reused. Use a new Builder instance instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import android.os.RemoteCallback;
|
||||
*/
|
||||
oneway interface IAmbientContextDetectionService {
|
||||
void startDetection(in AmbientContextEventRequest request, in String packageName,
|
||||
in RemoteCallback callback);
|
||||
in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback);
|
||||
void stopDetection(in String packageName);
|
||||
void queryServiceStatus(in int[] eventTypes, in String packageName,
|
||||
in RemoteCallback callback);
|
||||
}
|
||||
@@ -4082,6 +4082,16 @@
|
||||
-->
|
||||
<string name="config_defaultAmbientContextDetectionService" translatable="false"></string>
|
||||
|
||||
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent. -->
|
||||
<string translatable="false" name="config_defaultAmbientContextConsentComponent"></string>
|
||||
|
||||
<!-- Intent extra key for the caller's package name while requesting ambient context consent.
|
||||
-->
|
||||
<string translatable="false" name="config_ambientContextPackageNameExtraKey"></string>
|
||||
|
||||
<!-- Intent extra key for the event code int array while requesting ambient context consent. -->
|
||||
<string translatable="false" name="config_ambientContextEventArrayExtraKey"></string>
|
||||
|
||||
<!-- The component name for the system-wide captions service.
|
||||
This service must be trusted, as it controls part of the UI of the volume bar.
|
||||
Example: "com.android.captions/.SystemCaptionsService"
|
||||
|
||||
@@ -20,22 +20,30 @@ import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.BroadcastOptions;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ambientcontext.AmbientContextEvent;
|
||||
import android.app.ambientcontext.AmbientContextEventRequest;
|
||||
import android.app.ambientcontext.AmbientContextEventResponse;
|
||||
import android.app.ambientcontext.AmbientContextManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.service.ambientcontext.AmbientContextDetectionService;
|
||||
import android.provider.Settings;
|
||||
import android.service.ambientcontext.AmbientContextDetectionResult;
|
||||
import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
|
||||
@@ -44,7 +52,8 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.infra.AbstractPerUserSystemService;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -60,14 +69,12 @@ final class AmbientContextManagerPerUserService extends
|
||||
RemoteAmbientContextDetectionService mRemoteService;
|
||||
|
||||
private ComponentName mComponentName;
|
||||
private Context mContext;
|
||||
private Set<PendingIntent> mExistingPendingIntents;
|
||||
|
||||
AmbientContextManagerPerUserService(
|
||||
@NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) {
|
||||
super(master, lock, userId);
|
||||
mContext = master.getContext();
|
||||
mExistingPendingIntents = new HashSet<>();
|
||||
mExistingPendingIntents = new ArraySet<>();
|
||||
}
|
||||
|
||||
void destroyLocked() {
|
||||
@@ -154,12 +161,13 @@ final class AmbientContextManagerPerUserService extends
|
||||
* package. A new registration from the same package will overwrite the previous registration.
|
||||
*/
|
||||
public void onRegisterObserver(AmbientContextEventRequest request,
|
||||
PendingIntent pendingIntent) {
|
||||
PendingIntent pendingIntent, RemoteCallback clientStatusCallback) {
|
||||
synchronized (mLock) {
|
||||
if (!setUpServiceIfNeeded()) {
|
||||
Slog.w(TAG, "Service is not available at this moment.");
|
||||
sendStatusUpdateIntent(
|
||||
pendingIntent, AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
|
||||
Slog.w(TAG, "Detection service is not available at this moment.");
|
||||
sendStatusCallback(
|
||||
clientStatusCallback,
|
||||
AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -173,29 +181,58 @@ final class AmbientContextManagerPerUserService extends
|
||||
}
|
||||
|
||||
// Register new package and add request to mExistingRequests
|
||||
startDetection(request, callingPackage, createRemoteCallback());
|
||||
startDetection(request, callingPackage, createDetectionResultRemoteCallback(),
|
||||
getServerStatusCallback(clientStatusCallback));
|
||||
mExistingPendingIntents.add(pendingIntent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a RemoteCallback that handles the status from the detection service, and
|
||||
* sends results to the client callback.
|
||||
*/
|
||||
private RemoteCallback getServerStatusCallback(RemoteCallback clientStatusCallback) {
|
||||
return new RemoteCallback(result -> {
|
||||
AmbientContextDetectionServiceStatus serviceStatus =
|
||||
(AmbientContextDetectionServiceStatus) result.get(
|
||||
AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
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();
|
||||
Slog.i(TAG, "Got detection status of " + statusCode
|
||||
+ " for " + packageName);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void startDetection(AmbientContextEventRequest request, String callingPackage,
|
||||
RemoteCallback callback) {
|
||||
RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
|
||||
Slog.d(TAG, "Requested detection of " + request.getEventTypes());
|
||||
synchronized (mLock) {
|
||||
ensureRemoteServiceInitiated();
|
||||
mRemoteService.startDetection(request, callingPackage, callback);
|
||||
mRemoteService.startDetection(request, callingPackage, detectionResultCallback,
|
||||
statusCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an intent with a status code and empty events.
|
||||
*/
|
||||
void sendStatusUpdateIntent(PendingIntent pendingIntent, int statusCode) {
|
||||
AmbientContextEventResponse response = new AmbientContextEventResponse.Builder()
|
||||
.setStatusCode(statusCode)
|
||||
.build();
|
||||
sendResponseIntent(pendingIntent, response);
|
||||
void sendStatusCallback(RemoteCallback statusCallback, int statusCode) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(
|
||||
AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
|
||||
statusCode);
|
||||
statusCallback.sendResult(bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,6 +254,113 @@ final class AmbientContextManagerPerUserService extends
|
||||
}
|
||||
}
|
||||
|
||||
public void onQueryServiceStatus(int[] eventTypes, String callingPackage,
|
||||
RemoteCallback statusCallback) {
|
||||
Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes)
|
||||
+ " for " + callingPackage);
|
||||
synchronized (mLock) {
|
||||
if (!setUpServiceIfNeeded()) {
|
||||
Slog.w(TAG, "Detection service is not available at this moment.");
|
||||
sendStatusToCallback(statusCallback,
|
||||
AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
ensureRemoteServiceInitiated();
|
||||
mRemoteService.queryServiceStatus(
|
||||
eventTypes,
|
||||
callingPackage,
|
||||
getServerStatusCallback(statusCallback));
|
||||
}
|
||||
}
|
||||
|
||||
public void onStartConsentActivity(int[] eventTypes, String callingPackage) {
|
||||
Slog.d(TAG, "Opening consent activity of " + Arrays.toString(eventTypes)
|
||||
+ " for " + callingPackage);
|
||||
|
||||
// Look up the recent task from the callingPackage
|
||||
ActivityManager.RecentTaskInfo task;
|
||||
ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks;
|
||||
int userId = getUserId();
|
||||
try {
|
||||
recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1,
|
||||
/*flags*/ 0, userId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to query recent tasks!");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((recentTasks != null) || recentTasks.getList().isEmpty()) {
|
||||
Slog.e(TAG, "Recent task list is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
task = recentTasks.getList().get(0);
|
||||
if (!callingPackage.equals(task.topActivityInfo.packageName)) {
|
||||
Slog.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName
|
||||
+ " doesn't match with camera client package name: " + callingPackage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start activity as the same task from the callingPackage
|
||||
Intent intent = new Intent();
|
||||
ComponentName consentComponent = getConsentComponent();
|
||||
if (consentComponent != null) {
|
||||
Slog.e(TAG, "Starting consent activity for " + callingPackage);
|
||||
Context context = getContext();
|
||||
String packageNameExtraKey = Settings.Secure.getStringForUser(
|
||||
context.getContentResolver(),
|
||||
Settings.Secure.AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY,
|
||||
userId);
|
||||
String eventArrayExtraKey = Settings.Secure.getStringForUser(
|
||||
context.getContentResolver(),
|
||||
Settings.Secure.AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY,
|
||||
userId);
|
||||
|
||||
// Create consent activity intent with the calling package name and requested events.
|
||||
intent.setComponent(consentComponent);
|
||||
if (packageNameExtraKey != null) {
|
||||
intent.putExtra(packageNameExtraKey, callingPackage);
|
||||
}
|
||||
if (eventArrayExtraKey != null) {
|
||||
intent.putExtra(eventArrayExtraKey, eventTypes);
|
||||
}
|
||||
|
||||
// Set parent to the calling app's task
|
||||
ActivityOptions options = ActivityOptions.makeBasic();
|
||||
options.setLaunchTaskId(task.taskId);
|
||||
context.startActivity(intent, options.toBundle());
|
||||
} else {
|
||||
Slog.e(TAG, "Consent component not found!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the consent activity component from config lookup.
|
||||
*/
|
||||
private ComponentName getConsentComponent() {
|
||||
Context context = getContext();
|
||||
String consentComponent = Settings.Secure.getStringForUser(
|
||||
context.getContentResolver(),
|
||||
Settings.Secure.AMBIENT_CONTEXT_CONSENT_COMPONENT,
|
||||
getUserId());
|
||||
if (TextUtils.isEmpty(consentComponent)) {
|
||||
return null;
|
||||
}
|
||||
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
|
||||
void stopDetection(String packageName) {
|
||||
Slog.d(TAG, "Stop detection for " + packageName);
|
||||
@@ -240,12 +384,13 @@ final class AmbientContextManagerPerUserService extends
|
||||
* Sends out the Intent to the client after the event is detected.
|
||||
*
|
||||
* @param pendingIntent Client's PendingIntent for callback
|
||||
* @param response Response with status code and detection result
|
||||
* @param result result from the detection service
|
||||
*/
|
||||
private void sendResponseIntent(PendingIntent pendingIntent,
|
||||
AmbientContextEventResponse response) {
|
||||
private void sendDetectionResultIntent(PendingIntent pendingIntent,
|
||||
AmbientContextDetectionResult result) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE, response);
|
||||
intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS,
|
||||
new ArrayList(result.getEvents()));
|
||||
// Explicitly disallow the receiver from starting activities, to prevent apps from utilizing
|
||||
// the PendingIntent as a backdoor to do this.
|
||||
BroadcastOptions options = BroadcastOptions.makeBasic();
|
||||
@@ -254,38 +399,29 @@ final class AmbientContextManagerPerUserService extends
|
||||
pendingIntent.send(getContext(), 0, intent, null, null, null,
|
||||
options.toBundle());
|
||||
Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
|
||||
+ response);
|
||||
+ result);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private RemoteCallback createRemoteCallback() {
|
||||
private RemoteCallback createDetectionResultRemoteCallback() {
|
||||
return new RemoteCallback(result -> {
|
||||
AmbientContextEventResponse response = (AmbientContextEventResponse) result.get(
|
||||
AmbientContextDetectionService.RESPONSE_BUNDLE_KEY);
|
||||
AmbientContextDetectionResult detectionResult =
|
||||
(AmbientContextDetectionResult) result.get(
|
||||
AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
Set<PendingIntent> pendingIntentForFailedRequests = new HashSet<>();
|
||||
for (PendingIntent pendingIntent : mExistingPendingIntents) {
|
||||
// Send PendingIntent if a requesting package matches the response packages.
|
||||
if (response.getPackageName().equals(pendingIntent.getCreatorPackage())) {
|
||||
sendResponseIntent(pendingIntent, response);
|
||||
|
||||
int statusCode = response.getStatusCode();
|
||||
if (statusCode != AmbientContextEventResponse.STATUS_SUCCESS) {
|
||||
pendingIntentForFailedRequests.add(pendingIntent);
|
||||
}
|
||||
Slog.i(TAG, "Got response of " + response.getEvents() + " for "
|
||||
+ pendingIntent.getCreatorPackage() + ". Status: " + statusCode);
|
||||
// Send PendingIntent to requesting packages
|
||||
String creatorPackage = pendingIntent.getCreatorPackage();
|
||||
if (detectionResult.getPackageName().equals(creatorPackage)) {
|
||||
sendDetectionResultIntent(pendingIntent, detectionResult);
|
||||
Slog.i(TAG, "Got detection result of " + detectionResult.getEvents()
|
||||
+ " for " + creatorPackage);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the failed requests from the existing requests.
|
||||
for (PendingIntent pendingIntent : pendingIntentForFailedRequests) {
|
||||
mExistingPendingIntents.remove(pendingIntent);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import android.annotation.UserIdInt;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ambientcontext.AmbientContextEvent;
|
||||
import android.app.ambientcontext.AmbientContextEventRequest;
|
||||
import android.app.ambientcontext.AmbientContextEventResponse;
|
||||
import android.app.ambientcontext.IAmbientContextEventObserver;
|
||||
import android.app.ambientcontext.AmbientContextManager;
|
||||
import android.app.ambientcontext.IAmbientContextManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
@@ -76,7 +76,7 @@ public class AmbientContextManagerService extends
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextEventObserver());
|
||||
publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextManagerInternal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,14 +128,16 @@ public class AmbientContextManagerService extends
|
||||
* specified events. Intended for use by shell command for testing.
|
||||
* Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
|
||||
*/
|
||||
void startAmbientContextEvent(@UserIdInt int userId, AmbientContextEventRequest request,
|
||||
String packageName, RemoteCallback callback) {
|
||||
void startDetection(@UserIdInt int userId, AmbientContextEventRequest request,
|
||||
String packageName, RemoteCallback detectionResultCallback,
|
||||
RemoteCallback statusCallback) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
|
||||
synchronized (mLock) {
|
||||
final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.startDetection(request, packageName, callback);
|
||||
service.startDetection(request, packageName, detectionResultCallback,
|
||||
statusCallback);
|
||||
} else {
|
||||
Slog.i(TAG, "service not available for user_id: " + userId);
|
||||
}
|
||||
@@ -160,6 +162,25 @@ public class AmbientContextManagerService extends
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request to the remote AmbientContextDetectionService impl to query the status of the
|
||||
* specified events. Intended for use by shell command for testing.
|
||||
* Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
|
||||
*/
|
||||
void queryServiceStatus(@UserIdInt int userId, String packageName,
|
||||
int[] eventTypes, RemoteCallback callback) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
|
||||
synchronized (mLock) {
|
||||
final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.onQueryServiceStatus(eventTypes, packageName, callback);
|
||||
} else {
|
||||
Slog.i(TAG, "query service not available for user_id: " + userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AmbientContextManagerPerUserService component for this user.
|
||||
*/
|
||||
@@ -173,33 +194,64 @@ public class AmbientContextManagerService extends
|
||||
return null;
|
||||
}
|
||||
|
||||
private final class AmbientContextEventObserver extends IAmbientContextEventObserver.Stub {
|
||||
private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub {
|
||||
final AmbientContextManagerPerUserService mService = getServiceForUserLocked(
|
||||
UserHandle.getCallingUserId());
|
||||
|
||||
@Override
|
||||
public void registerObserver(
|
||||
AmbientContextEventRequest request, PendingIntent pendingIntent) {
|
||||
AmbientContextEventRequest request, PendingIntent resultPendingIntent,
|
||||
RemoteCallback statusCallback) {
|
||||
Objects.requireNonNull(request);
|
||||
Objects.requireNonNull(pendingIntent);
|
||||
Objects.requireNonNull(resultPendingIntent);
|
||||
Objects.requireNonNull(statusCallback);
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
|
||||
assertCalledByPackageOwner(resultPendingIntent.getCreatorPackage());
|
||||
if (!mIsServiceEnabled) {
|
||||
Slog.w(TAG, "Service not available.");
|
||||
mService.sendStatusUpdateIntent(pendingIntent,
|
||||
AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
|
||||
mService.sendStatusCallback(statusCallback,
|
||||
AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
mService.onRegisterObserver(request, pendingIntent);
|
||||
mService.onRegisterObserver(request, resultPendingIntent, statusCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObserver(String callingPackage) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
|
||||
assertCalledByPackageOwner(callingPackage);
|
||||
mService.onUnregisterObserver(callingPackage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryServiceStatus(int[] eventTypes, String callingPackage,
|
||||
RemoteCallback statusCallback) {
|
||||
Objects.requireNonNull(eventTypes);
|
||||
Objects.requireNonNull(callingPackage);
|
||||
Objects.requireNonNull(statusCallback);
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
|
||||
assertCalledByPackageOwner(callingPackage);
|
||||
if (!mIsServiceEnabled) {
|
||||
Slog.w(TAG, "Detection service not available.");
|
||||
mService.sendStatusToCallback(statusCallback,
|
||||
AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
mService.onQueryServiceStatus(eventTypes, callingPackage,
|
||||
statusCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startConsentActivity(int[] eventTypes, String callingPackage) {
|
||||
Objects.requireNonNull(eventTypes);
|
||||
Objects.requireNonNull(callingPackage);
|
||||
assertCalledByPackageOwner(callingPackage);
|
||||
mService.onStartConsentActivity(eventTypes, callingPackage);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
|
||||
|
||||
@@ -21,12 +21,12 @@ import static java.lang.System.out;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ambientcontext.AmbientContextEvent;
|
||||
import android.app.ambientcontext.AmbientContextEventRequest;
|
||||
import android.app.ambientcontext.AmbientContextEventResponse;
|
||||
import android.content.ComponentName;
|
||||
import android.os.Binder;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.ShellCommand;
|
||||
import android.service.ambientcontext.AmbientContextDetectionService;
|
||||
import android.service.ambientcontext.AmbientContextDetectionResult;
|
||||
import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -35,6 +35,12 @@ import java.io.PrintWriter;
|
||||
*/
|
||||
final class AmbientContextShellCommand extends ShellCommand {
|
||||
|
||||
private static final AmbientContextEventRequest REQUEST =
|
||||
new AmbientContextEventRequest.Builder()
|
||||
.addEventType(AmbientContextEvent.EVENT_COUGH)
|
||||
.addEventType(AmbientContextEvent.EVENT_SNORE)
|
||||
.build();
|
||||
|
||||
@NonNull
|
||||
private final AmbientContextManagerService mService;
|
||||
|
||||
@@ -44,22 +50,43 @@ final class AmbientContextShellCommand extends ShellCommand {
|
||||
|
||||
/** Callbacks for AmbientContextEventService results used internally for testing. */
|
||||
static class TestableCallbackInternal {
|
||||
private AmbientContextEventResponse mLastResponse;
|
||||
private AmbientContextDetectionResult mLastResult;
|
||||
private AmbientContextDetectionServiceStatus mLastStatus;
|
||||
|
||||
public AmbientContextEventResponse getLastResponse() {
|
||||
return mLastResponse;
|
||||
public AmbientContextDetectionResult getLastResult() {
|
||||
return mLastResult;
|
||||
}
|
||||
|
||||
public AmbientContextDetectionServiceStatus getLastStatus() {
|
||||
return mLastStatus;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private RemoteCallback createRemoteCallback() {
|
||||
private RemoteCallback createRemoteDetectionResultCallback() {
|
||||
return new RemoteCallback(result -> {
|
||||
AmbientContextEventResponse response =
|
||||
(AmbientContextEventResponse) result.get(
|
||||
AmbientContextDetectionService.RESPONSE_BUNDLE_KEY);
|
||||
AmbientContextDetectionResult detectionResult =
|
||||
(AmbientContextDetectionResult) result.get(
|
||||
AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mLastResponse = response;
|
||||
out.println("Response available: " + response);
|
||||
mLastResult = detectionResult;
|
||||
out.println("Detection result available: " + detectionResult);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private RemoteCallback createRemoteStatusCallback() {
|
||||
return new RemoteCallback(result -> {
|
||||
AmbientContextDetectionServiceStatus status =
|
||||
(AmbientContextDetectionServiceStatus) result.get(
|
||||
AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mLastStatus = status;
|
||||
out.println("Status available: " + status);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
@@ -83,6 +110,8 @@ final class AmbientContextShellCommand extends ShellCommand {
|
||||
return runStopDetection();
|
||||
case "get-last-status-code":
|
||||
return getLastStatusCode();
|
||||
case "query-service-status":
|
||||
return runQueryServiceStatus();
|
||||
case "get-bound-package":
|
||||
return getBoundPackageName();
|
||||
case "set-temporary-service":
|
||||
@@ -95,13 +124,9 @@ final class AmbientContextShellCommand extends ShellCommand {
|
||||
private int runStartDetection() {
|
||||
final int userId = Integer.parseInt(getNextArgRequired());
|
||||
final String packageName = getNextArgRequired();
|
||||
AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
|
||||
.addEventType(AmbientContextEvent.EVENT_COUGH)
|
||||
.addEventType(AmbientContextEvent.EVENT_SNORE)
|
||||
.build();
|
||||
|
||||
mService.startAmbientContextEvent(userId, request, packageName,
|
||||
sTestableCallbackInternal.createRemoteCallback());
|
||||
mService.startDetection(userId, REQUEST, packageName,
|
||||
sTestableCallbackInternal.createRemoteDetectionResultCallback(),
|
||||
sTestableCallbackInternal.createRemoteStatusCallback());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -112,8 +137,20 @@ final class AmbientContextShellCommand extends ShellCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runQueryServiceStatus() {
|
||||
final int userId = Integer.parseInt(getNextArgRequired());
|
||||
final String packageName = getNextArgRequired();
|
||||
int[] types = new int[] {
|
||||
AmbientContextEvent.EVENT_COUGH,
|
||||
AmbientContextEvent.EVENT_SNORE};
|
||||
mService.queryServiceStatus(userId, packageName, types,
|
||||
sTestableCallbackInternal.createRemoteStatusCallback());
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getLastStatusCode() {
|
||||
AmbientContextEventResponse lastResponse = sTestableCallbackInternal.getLastResponse();
|
||||
AmbientContextDetectionServiceStatus lastResponse =
|
||||
sTestableCallbackInternal.getLastStatus();
|
||||
if (lastResponse == null) {
|
||||
return -1;
|
||||
}
|
||||
@@ -130,6 +167,7 @@ final class AmbientContextShellCommand extends ShellCommand {
|
||||
pw.println(" start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection.");
|
||||
pw.println(" stop-detection USER_ID: Stops AmbientContextEvent detection.");
|
||||
pw.println(" get-last-status-code: Prints the latest request status code.");
|
||||
pw.println(" query-event-status USER_ID PACKAGE_NAME: Prints the event status code.");
|
||||
pw.println(" get-bound-package USER_ID:"
|
||||
+ " Print the bound package that implements the service.");
|
||||
pw.println(" set-temporary-service USER_ID [COMPONENT_NAME DURATION]");
|
||||
|
||||
@@ -20,6 +20,7 @@ import static android.content.Context.BIND_FOREGROUND_SERVICE;
|
||||
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ambientcontext.AmbientContextEvent;
|
||||
import android.app.ambientcontext.AmbientContextEventRequest;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -53,13 +54,15 @@ final class RemoteAmbientContextDetectionService
|
||||
*
|
||||
* @param request The request with events to detect, and optional detection options.
|
||||
* @param packageName The app package that requested the detection
|
||||
* @param callback callback for detection results
|
||||
* @param detectionResultCallback callback for detection results
|
||||
* @param statusCallback callback for service status
|
||||
*/
|
||||
public void startDetection(
|
||||
@NonNull AmbientContextEventRequest request, String packageName,
|
||||
RemoteCallback callback) {
|
||||
RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
|
||||
Slog.i(TAG, "Start detection for " + request.getEventTypes());
|
||||
post(service -> service.startDetection(request, packageName, callback));
|
||||
post(service -> service.startDetection(request, packageName, detectionResultCallback,
|
||||
statusCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,4 +74,15 @@ final class RemoteAmbientContextDetectionService
|
||||
Slog.i(TAG, "Stop detection for " + packageName);
|
||||
post(service -> service.stopDetection(packageName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the implementation to return the event status for the package.
|
||||
*/
|
||||
public void queryServiceStatus(
|
||||
@AmbientContextEvent.EventCode int[] eventTypes,
|
||||
String packageName,
|
||||
RemoteCallback callback) {
|
||||
Slog.i(TAG, "Query status for " + packageName);
|
||||
post(service -> service.queryServiceStatus(eventTypes, packageName, callback));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user