294 lines
9.8 KiB
Java
294 lines
9.8 KiB
Java
/*
|
|
* 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.
|
|
*
|
|
* <p>
|
|
* Usage: <pre> {@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();
|
|
* }
|
|
*
|
|
* }</pre>
|
|
*
|
|
* @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<OnTargetsAvailableListener, CallbackWrapper> mRegisteredCallbacks =
|
|
new ArrayMap<>();
|
|
|
|
/**
|
|
* Creates a new Smartspace ui client.
|
|
* <p>
|
|
* 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<SmartspaceTarget> targets);
|
|
}
|
|
|
|
static class CallbackWrapper extends Stub {
|
|
|
|
private final Consumer<List<SmartspaceTarget>> mCallback;
|
|
private final Executor mExecutor;
|
|
|
|
CallbackWrapper(@NonNull Executor callbackExecutor,
|
|
@NonNull Consumer<List<SmartspaceTarget>> 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;
|
|
}
|
|
}
|