diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2018-11-14 11:59:02 -0800 |
---|---|---|
committer | Winson Chung <winsonc@google.com> | 2019-01-10 15:53:25 -0800 |
commit | 54e91344e2a0072c40d09405fe5a295467b36c07 (patch) | |
tree | 8e25640c130c1ca8e6975a513c69a21f1961ba02 /services/appprediction/java | |
parent | 00112e6f6f23edf15cc01be5fd099bc0f88cdd4f (diff) |
Adding initial implementation of Prediction client/service API
Test: Build sample app, ensure that app prediction service gets client
requests
Bug: 111701043
Change-Id: I33aceb2de31552b2d740dc333559d68728753e40
Signed-off-by: Winson Chung <winsonc@google.com>
Diffstat (limited to 'services/appprediction/java')
4 files changed, 599 insertions, 0 deletions
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java new file mode 100644 index 000000000000..833eaa06d759 --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2018 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 com.android.server.appprediction; + +import static android.Manifest.permission.MANAGE_APP_PREDICTIONS; +import static android.content.Context.APP_PREDICTION_SERVICE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.IPredictionCallback; +import android.app.prediction.IPredictionManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.UserHandle; + +import com.android.server.infra.AbstractMasterSystemService; +import com.android.server.infra.FrameworkResourcesServiceNameResolver; + +import java.io.FileDescriptor; +import java.util.function.Consumer; + +/** + * A service used to predict app and shortcut usage. + * + * <p>The data collected by this service can be analyzed and combined with other sources to provide + * predictions in different areas of the system such as Launcher and Share sheet. + */ +public class AppPredictionManagerService extends + AbstractMasterSystemService<AppPredictionManagerService, AppPredictionPerUserService> { + + private static final String TAG = AppPredictionManagerService.class.getSimpleName(); + + private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes + + public AppPredictionManagerService(Context context) { + super(context, new FrameworkResourcesServiceNameResolver(context, + com.android.internal.R.string.config_defaultAppPredictionService), null); + } + + @Override + protected AppPredictionPerUserService newServiceLocked(int resolvedUserId, boolean disabled) { + return new AppPredictionPerUserService(this, mLock, resolvedUserId); + } + + @Override + public void onStart() { + publishBinderService(APP_PREDICTION_SERVICE, new PredictionManagerServiceStub()); + } + + @Override + protected void enforceCallingPermissionForManagement() { + getContext().enforceCallingPermission(MANAGE_APP_PREDICTIONS, TAG); + } + + @Override + protected int getMaximumTemporaryServiceDurationMs() { + return MAX_TEMP_SERVICE_DURATION_MS; + } + + private class PredictionManagerServiceStub extends IPredictionManager.Stub { + + @Override + public void createPredictionSession(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) { + runForUserLocked((service) -> + service.onCreatePredictionSessionLocked(context, sessionId)); + } + + @Override + public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, + @NonNull AppTargetEvent event) { + runForUserLocked((service) -> service.notifyAppTargetEventLocked(sessionId, event)); + } + + @Override + public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId, + @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { + runForUserLocked((service) -> + service.notifyLocationShownLocked(sessionId, launchLocation, targetIds)); + } + + @Override + public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, + @NonNull ParceledListSlice targets, + IPredictionCallback callback) { + runForUserLocked((service) -> + service.sortAppTargetsLocked(sessionId, targets, callback)); + } + + @Override + public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + runForUserLocked((service) -> + service.registerPredictionUpdatesLocked(sessionId, callback)); + } + + public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + runForUserLocked((service) -> + service.unregisterPredictionUpdatesLocked(sessionId, callback)); + } + + @Override + public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { + runForUserLocked((service) -> service.requestPredictionUpdateLocked(sessionId)); + } + + @Override + public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { + runForUserLocked((service) -> service.onDestroyPredictionSessionLocked(sessionId)); + } + + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new AppPredictionManagerServiceShellCommand(AppPredictionManagerService.this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + private void runForUserLocked(@NonNull Consumer<AppPredictionPerUserService> c) { + final int userId = UserHandle.getCallingUserId(); + // TODO(b/111701043): Determine what permission model we want for this + long origId = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + final AppPredictionPerUserService service = getServiceForUserLocked(userId); + c.accept(service); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java new file mode 100644 index 000000000000..ed7cc67aa46f --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 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 com.android.server.appprediction; + +import android.annotation.NonNull; +import android.os.ShellCommand; + +import java.io.PrintWriter; + +/** + * The shell command implementation for the AppPredictionManagerService. + */ +public class AppPredictionManagerServiceShellCommand extends ShellCommand { + + private static final String TAG = + AppPredictionManagerServiceShellCommand.class.getSimpleName(); + + private final AppPredictionManagerService mService; + + public AppPredictionManagerServiceShellCommand(@NonNull AppPredictionManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "set": { + final String what = getNextArgRequired(); + switch (what) { + case "temporary-service": { + final int userId = Integer.parseInt(getNextArgRequired()); + String serviceName = getNextArg(); + if (serviceName == null) { + mService.resetTemporaryService(userId); + return 0; + } + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryService(userId, serviceName, duration); + pw.println("AppPredictionService temporarily set to " + serviceName + + " for " + duration + "ms"); + break; + } + } + } + break; + default: + return handleDefaultCommands(cmd); + } + return 0; + } + + @Override + public void onHelp() { + try (PrintWriter pw = getOutPrintWriter()) { + pw.println("AppPredictionManagerService commands:"); + pw.println(" help"); + pw.println(" Prints this help text."); + pw.println(""); + pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implemtation."); + pw.println(" To reset, call with just the USER_ID argument."); + pw.println(""); + } + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java new file mode 100644 index 000000000000..24d592c38afb --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2018 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 com.android.server.appprediction; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppGlobals; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.IPredictionCallback; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ParceledListSlice; +import android.content.pm.ServiceInfo; +import android.os.RemoteException; +import android.service.appprediction.AppPredictionService; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.infra.AbstractPerUserSystemService; + +/** + * Per-user instance of {@link AppPredictionManagerService}. + */ +public class AppPredictionPerUserService extends + AbstractPerUserSystemService<AppPredictionPerUserService, AppPredictionManagerService> + implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks { + + private static final String TAG = AppPredictionPerUserService.class.getSimpleName(); + + @Nullable + @GuardedBy("mLock") + private RemoteAppPredictionService mRemoteService; + + protected AppPredictionPerUserService(AppPredictionManagerService master, + Object lock, int userId) { + super(master, lock, userId); + } + + @Override // from PerUserSystemService + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) + throws NameNotFoundException { + + ServiceInfo si; + try { + si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, + PackageManager.GET_META_DATA, mUserId); + } catch (RemoteException e) { + throw new NameNotFoundException("Could not get service for " + serviceComponent); + } + // TODO(b/111701043): must check that either the service is from a system component, + // or it matches a service set by shell cmd (so it can be used on CTS tests and when + // OEMs are implementing the real service and also verify the proper permissions + return si; + } + + @GuardedBy("mLock") + @Override // from PerUserSystemService + protected boolean updateLocked(boolean disabled) { + final boolean enabledChanged = super.updateLocked(disabled); + if (enabledChanged) { + if (!isEnabledLocked()) { + // Clear the remote service for the next call + mRemoteService = null; + } + } + return enabledChanged; + } + + /** + * Notifies the service of a new prediction session. + */ + @GuardedBy("mLock") + public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.onCreatePredictionSession(context, sessionId); + } + } + + /** + * Records an app target event to the service. + */ + @GuardedBy("mLock") + public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull AppTargetEvent event) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.notifyAppTargetEvent(sessionId, event); + } + } + + /** + * Records when a launch location is shown. + */ + @GuardedBy("mLock") + public void notifyLocationShownLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.notifyLocationShown(sessionId, launchLocation, targetIds); + } + } + + /** + * Requests the service to sort a list of apps or shortcuts. + */ + @GuardedBy("mLock") + public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.sortAppTargets(sessionId, targets, callback); + } + } + + /** + * Registers a callback for continuous updates of predicted apps or shortcuts. + */ + @GuardedBy("mLock") + public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.registerPredictionUpdates(sessionId, callback); + } + } + + /** + * Unregisters a callback for continuous updates of predicted apps or shortcuts. + */ + @GuardedBy("mLock") + public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.unregisterPredictionUpdates(sessionId, callback); + } + } + + /** + * Requests a new set of predicted apps or shortcuts. + */ + @GuardedBy("mLock") + public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.requestPredictionUpdate(sessionId); + } + } + + /** + * Notifies the service of the end of an existing prediction session. + */ + @GuardedBy("mLock") + public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.onDestroyPredictionSession(sessionId); + } + } + + @Override + public void onFailureOrTimeout(boolean timedOut) { + if (isDebug()) { + Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut); + } + + // Do nothing, we are just proxying to the prediction service + } + + @Override + public void onServiceDied(RemoteAppPredictionService service) { + if (isDebug()) { + Slog.d(TAG, "onServiceDied():"); + } + + // Do nothing, we are just proxying to the prediction service + } + + @GuardedBy("mLock") + @Nullable + private RemoteAppPredictionService getRemoteServiceLocked() { + if (mRemoteService == null) { + final String serviceName = getComponentNameLocked(); + if (serviceName == null) { + if (mMaster.verbose) { + Slog.v(TAG, "getRemoteServiceLocked(): not set"); + } + return null; + } + ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); + + mRemoteService = new RemoteAppPredictionService(getContext(), + AppPredictionService.SERVICE_INTERFACE, serviceComponent, mUserId, this, + mMaster.isBindInstantServiceAllowed(), mMaster.verbose); + } + + return mRemoteService; + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java new file mode 100644 index 000000000000..45ea86f9e74e --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018 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 com.android.server.appprediction; + +import android.annotation.NonNull; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.IPredictionCallback; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.IBinder; +import android.service.appprediction.IPredictionService; +import android.text.format.DateUtils; + +import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; + + +/** + * Proxy to the {@link android.service.appprediction.AppPredictionService} implemention in another + * process. + */ +public class RemoteAppPredictionService extends + AbstractMultiplePendingRequestsRemoteService<RemoteAppPredictionService, + IPredictionService> { + + private static final String TAG = "RemoteAppPredictionService"; + + private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + + public RemoteAppPredictionService(Context context, String serviceInterface, + ComponentName componentName, int userId, + RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed, + boolean verbose) { + super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed, + verbose, /* initialCapacity= */ 1); + } + + @Override + protected IPredictionService getServiceInterface(IBinder service) { + return IPredictionService.Stub.asInterface(service); + } + + @Override + protected long getTimeoutIdleBindMillis() { + return PERMANENT_BOUND_TIMEOUT_MS; + } + + @Override + protected long getRemoteRequestMillis() { + return TIMEOUT_REMOTE_REQUEST_MILLIS; + } + + /** + * Notifies the service of a new prediction session. + */ + public void onCreatePredictionSession(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) { + scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId)); + } + + /** + * Records an app target event to the service. + */ + public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, + @NonNull AppTargetEvent event) { + scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event)); + } + + /** + * Records when a launch location is shown. + */ + public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId, + @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { + scheduleAsyncRequest((s) -> s.notifyLocationShown(sessionId, launchLocation, targetIds)); + } + + /** + * Requests the service to sort a list of apps or shortcuts. + */ + public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, + @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { + scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback)); + } + + + /** + * Registers a callback for continuous updates of predicted apps or shortcuts. + */ + public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback)); + } + + /** + * Unregisters a callback for continuous updates of predicted apps or shortcuts. + */ + public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback)); + } + + /** + * Requests a new set of predicted apps or shortcuts. + */ + public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { + scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId)); + } + + /** + * Notifies the service of the end of an existing prediction session. + */ + public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { + scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId)); + } + + /** + * Failure callback + */ + public interface RemoteAppPredictionServiceCallbacks + extends VultureCallback<RemoteAppPredictionService> { + + /** + * Notifies a the failure or timeout of a remote call. + */ + void onFailureOrTimeout(boolean timedOut); + } +} |