/* * 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.smartspace; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.smartspace.ISmartspaceCallback.Stub; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import dalvik.system.CloseGuard; import java.util.List; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; /** * Client API to share information about the Smartspace UI state and execute query. * *
* Usage:
{@code * * class MyActivity { * private SmartspaceSession mSmartspaceSession; * * void onCreate() { * mSmartspaceSession = mSmartspaceManager.createSmartspaceSession(smartspaceConfig) * mSmartspaceSession.registerSmartspaceUpdates(...) * } * * void onStart() { * mSmartspaceSession.requestSmartspaceUpdate() * } * * void onTouch(...) OR * void onStateTransitionStarted(...) OR * void onResume(...) OR * void onStop(...) { * mSmartspaceSession.notifyEvent(event); * } * * void onDestroy() { * mSmartspaceSession.unregisterPredictionUpdates() * mSmartspaceSession.close(); * } * * }* * @hide */ @SystemApi public final class SmartspaceSession implements AutoCloseable { private static final String TAG = SmartspaceSession.class.getSimpleName(); private static final boolean DEBUG = false; private final android.app.smartspace.ISmartspaceManager mInterface; private final CloseGuard mCloseGuard = CloseGuard.get(); private final AtomicBoolean mIsClosed = new AtomicBoolean(false); private final SmartspaceSessionId mSessionId; private final ArrayMap
* The caller should call {@link SmartspaceSession#destroy()} to dispose the client once it
* no longer used.
*
* @param context the {@link Context} of the user of this {@link SmartspaceSession}.
* @param smartspaceConfig the Smartspace context.
*/
// b/177858121 Create weak reference child objects to not leak context.
SmartspaceSession(@NonNull Context context, @NonNull SmartspaceConfig smartspaceConfig) {
IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
mInterface = android.app.smartspace.ISmartspaceManager.Stub.asInterface(b);
mSessionId = new SmartspaceSessionId(
context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUser());
try {
mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, getToken());
} catch (RemoteException e) {
Log.e(TAG, "Failed to create Smartspace session", e);
e.rethrowFromSystemServer();
}
mCloseGuard.open("SmartspaceSession.close");
}
/**
* Notifies the Smartspace service of a Smartspace target event.
*
* @param event The {@link SmartspaceTargetEvent} that represents the Smartspace target event.
*/
public void notifySmartspaceEvent(@NonNull SmartspaceTargetEvent event) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
try {
mInterface.notifySmartspaceEvent(mSessionId, event);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify event", e);
e.rethrowFromSystemServer();
}
}
/**
* Requests the smartspace service for an update.
*/
public void requestSmartspaceUpdate() {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
try {
mInterface.requestSmartspaceUpdate(mSessionId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to request update.", e);
e.rethrowFromSystemServer();
}
}
/**
* Requests the smartspace service provide continuous updates of smartspace cards via the
* provided callback, until the given callback is unregistered.
*
* @param listenerExecutor The listener executor to use when firing the listener.
* @param listener The listener to be called when updates of Smartspace targets are
* available.
*/
public void addOnTargetsAvailableListener(@NonNull @CallbackExecutor Executor listenerExecutor,
@NonNull OnTargetsAvailableListener listener) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
if (mRegisteredCallbacks.containsKey(listener)) {
// Skip if this callback is already registered
return;
}
try {
final CallbackWrapper callbackWrapper = new CallbackWrapper(listenerExecutor,
listener::onTargetsAvailable);
mRegisteredCallbacks.put(listener, callbackWrapper);
mInterface.registerSmartspaceUpdates(mSessionId, callbackWrapper);
mInterface.requestSmartspaceUpdate(mSessionId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register for smartspace updates", e);
e.rethrowAsRuntimeException();
}
}
/**
* Requests the smartspace service to stop providing continuous updates to the provided
* callback until the callback is re-registered.
*
* @param listener The callback to be unregistered.
* @see {@link SmartspaceSession#addOnTargetsAvailableListener(Executor,
* OnTargetsAvailableListener)}.
*/
public void removeOnTargetsAvailableListener(@NonNull OnTargetsAvailableListener listener) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
if (!mRegisteredCallbacks.containsKey(listener)) {
// Skip if this callback was never registered
return;
}
try {
final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(listener);
mInterface.unregisterSmartspaceUpdates(mSessionId, callbackWrapper);
} catch (RemoteException e) {
Log.e(TAG, "Failed to unregister for smartspace updates", e);
e.rethrowAsRuntimeException();
}
}
/**
* Destroys the client and unregisters the callback. Any method on this class after this call
* will throw {@link IllegalStateException}.
*/
private void destroy() {
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
// Do destroy;
try {
mInterface.destroySmartspaceSession(mSessionId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to notify Smartspace target event", e);
e.rethrowFromSystemServer();
}
} else {
throw new IllegalStateException("This client has already been destroyed.");
}
}
@Override
protected void finalize() {
try {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
if (!mIsClosed.get()) {
destroy();
}
} finally {
try {
super.finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
@Override
public void close() {
try {
destroy();
finalize();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
/**
* Listener to receive smartspace targets from the service.
*/
public interface OnTargetsAvailableListener {
/**
* Called when a new set of smartspace targets are available.
*
* @param targets Ranked list of smartspace targets.
*/
void onTargetsAvailable(@NonNull List> mCallback;
private final Executor mExecutor;
CallbackWrapper(@NonNull Executor callbackExecutor,
@NonNull Consumer
> callback) {
mCallback = callback;
mExecutor = callbackExecutor;
}
@Override
public void onResult(ParceledListSlice result) {
final long identity = Binder.clearCallingIdentity();
try {
if (DEBUG) {
Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
}
mExecutor.execute(() -> mCallback.accept(result.getList()));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
private static class Token {
static final IBinder sBinder = new Binder(TAG);
}
private static IBinder getToken() {
return Token.sBinder;
}
}