Merge "Adding System Server implementation of Cloud Search API"
This commit is contained in:
committed by
Android (Google) Code Review
commit
8ac061f7e8
@@ -79,6 +79,7 @@ filegroup {
|
||||
":services.backup-sources",
|
||||
":services.bluetooth-sources", // TODO(b/214988855) : Remove once apex/service-bluetooth jar is ready
|
||||
":backuplib-sources",
|
||||
":services.cloudsearch-sources",
|
||||
":services.companion-sources",
|
||||
":services.contentcapture-sources",
|
||||
":services.contentsuggestions-sources",
|
||||
@@ -133,6 +134,7 @@ java_library {
|
||||
"services.appwidget",
|
||||
"services.autofill",
|
||||
"services.backup",
|
||||
"services.cloudsearch",
|
||||
"services.companion",
|
||||
"services.contentcapture",
|
||||
"services.contentsuggestions",
|
||||
|
||||
22
services/cloudsearch/Android.bp
Normal file
22
services/cloudsearch/Android.bp
Normal file
@@ -0,0 +1,22 @@
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_license"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "services.cloudsearch-sources",
|
||||
srcs: ["java/**/*.java"],
|
||||
path: "java",
|
||||
visibility: ["//frameworks/base/services"],
|
||||
}
|
||||
|
||||
java_library_static {
|
||||
name: "services.cloudsearch",
|
||||
defaults: ["platform_service_defaults"],
|
||||
srcs: [":services.cloudsearch-sources"],
|
||||
libs: ["services.core"],
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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 com.android.server.cloudsearch;
|
||||
|
||||
import static android.Manifest.permission.MANAGE_CLOUDSEARCH;
|
||||
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
|
||||
import static android.content.Context.CLOUDSEARCH_SERVICE;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.cloudsearch.ICloudSearchManager;
|
||||
import android.app.cloudsearch.ICloudSearchManagerCallback;
|
||||
import android.app.cloudsearch.SearchRequest;
|
||||
import android.app.cloudsearch.SearchResponse;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ShellCallback;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.infra.AbstractMasterSystemService;
|
||||
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
|
||||
import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A service used to return cloudsearch targets given a query.
|
||||
*/
|
||||
public class CloudSearchManagerService extends
|
||||
AbstractMasterSystemService<CloudSearchManagerService, CloudSearchPerUserService> {
|
||||
|
||||
private static final String TAG = CloudSearchManagerService.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
|
||||
|
||||
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
|
||||
|
||||
public CloudSearchManagerService(Context context) {
|
||||
super(context, new FrameworkResourcesServiceNameResolver(context,
|
||||
R.string.config_defaultCloudSearchService), null,
|
||||
PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
|
||||
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CloudSearchPerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
|
||||
return new CloudSearchPerUserService(this, mLock, resolvedUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(CLOUDSEARCH_SERVICE, new CloudSearchManagerStub());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enforceCallingPermissionForManagement() {
|
||||
getContext().enforceCallingPermission(MANAGE_CLOUDSEARCH, TAG);
|
||||
}
|
||||
|
||||
@Override // from AbstractMasterSystemService
|
||||
protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
|
||||
final CloudSearchPerUserService service = peekServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.onPackageUpdatedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from AbstractMasterSystemService
|
||||
protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
|
||||
final CloudSearchPerUserService service = peekServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.onPackageRestartedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMaximumTemporaryServiceDurationMs() {
|
||||
return MAX_TEMP_SERVICE_DURATION_MS;
|
||||
}
|
||||
|
||||
private class CloudSearchManagerStub extends ICloudSearchManager.Stub {
|
||||
|
||||
@Override
|
||||
public void search(@NonNull SearchRequest searchRequest,
|
||||
@NonNull ICloudSearchManagerCallback callBack) {
|
||||
runForUserLocked("search", searchRequest.getRequestId(), (service) ->
|
||||
service.onSearchLocked(searchRequest, callBack));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnResults(IBinder token, String requestId, SearchResponse response) {
|
||||
runForUserLocked("returnResults", requestId, (service) ->
|
||||
service.onReturnResultsLocked(token, requestId, response));
|
||||
}
|
||||
|
||||
public void destroy(@NonNull SearchRequest searchRequest) {
|
||||
runForUserLocked("destroyCloudSearchSession", searchRequest.getRequestId(),
|
||||
(service) -> service.onDestroyLocked(searchRequest.getRequestId()));
|
||||
}
|
||||
|
||||
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
|
||||
@Nullable FileDescriptor err,
|
||||
@NonNull String[] args, @Nullable ShellCallback callback,
|
||||
@NonNull ResultReceiver resultReceiver) {
|
||||
new CloudSearchManagerServiceShellCommand(CloudSearchManagerService.this)
|
||||
.exec(this, in, out, err, args, callback, resultReceiver);
|
||||
}
|
||||
|
||||
private void runForUserLocked(@NonNull final String func,
|
||||
@NonNull final String requestId,
|
||||
@NonNull final Consumer<CloudSearchPerUserService> c) {
|
||||
ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
|
||||
final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
|
||||
Binder.getCallingUserHandle().getIdentifier(), false, ALLOW_NON_FULL,
|
||||
null, null);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
|
||||
+ ", uid=" + Binder.getCallingUid());
|
||||
}
|
||||
Context ctx = getContext();
|
||||
if (!(ctx.checkCallingPermission(MANAGE_CLOUDSEARCH) == PERMISSION_GRANTED
|
||||
|| mServiceNameResolver.isTemporary(userId)
|
||||
|| mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
|
||||
|
||||
String msg = "Permission Denial: Cannot call " + func + " from pid="
|
||||
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
|
||||
Slog.w(TAG, msg);
|
||||
throw new SecurityException(msg);
|
||||
}
|
||||
|
||||
final long origId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
final CloudSearchPerUserService service = getServiceForUserLocked(userId);
|
||||
c.accept(service);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(origId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 com.android.server.cloudsearch;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.ShellCommand;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* The shell command implementation for the CloudSearchManagerService.
|
||||
*/
|
||||
public class CloudSearchManagerServiceShellCommand extends ShellCommand {
|
||||
|
||||
private static final String TAG =
|
||||
CloudSearchManagerServiceShellCommand.class.getSimpleName();
|
||||
|
||||
private final CloudSearchManagerService mService;
|
||||
|
||||
public CloudSearchManagerServiceShellCommand(@NonNull CloudSearchManagerService 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);
|
||||
pw.println("CloudSearchService temporarily reset. ");
|
||||
return 0;
|
||||
}
|
||||
final int duration = Integer.parseInt(getNextArgRequired());
|
||||
mService.setTemporaryService(userId, serviceName, duration);
|
||||
pw.println("CloudSearchService 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("CloudSearchManagerService 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("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* 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 com.android.server.cloudsearch;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.cloudsearch.ICloudSearchManagerCallback;
|
||||
import android.app.cloudsearch.SearchRequest;
|
||||
import android.app.cloudsearch.SearchResponse;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.service.cloudsearch.CloudSearchService;
|
||||
import android.service.cloudsearch.ICloudSearchService;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.infra.AbstractRemoteService;
|
||||
import com.android.server.CircularQueue;
|
||||
import com.android.server.infra.AbstractPerUserSystemService;
|
||||
|
||||
/**
|
||||
* Per-user instance of {@link CloudSearchManagerService}.
|
||||
*/
|
||||
public class CloudSearchPerUserService extends
|
||||
AbstractPerUserSystemService<CloudSearchPerUserService, CloudSearchManagerService>
|
||||
implements RemoteCloudSearchService.RemoteCloudSearchServiceCallbacks {
|
||||
|
||||
private static final String TAG = CloudSearchPerUserService.class.getSimpleName();
|
||||
private static final int QUEUE_SIZE = 10;
|
||||
@GuardedBy("mLock")
|
||||
private final CircularQueue<String, CloudSearchCallbackInfo> mCallbackQueue =
|
||||
new CircularQueue<>(QUEUE_SIZE);
|
||||
@Nullable
|
||||
@GuardedBy("mLock")
|
||||
private RemoteCloudSearchService mRemoteService;
|
||||
/**
|
||||
* When {@code true}, remote service died but service state is kept so it's restored after
|
||||
* the system re-binds to it.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private boolean mZombie;
|
||||
|
||||
protected CloudSearchPerUserService(CloudSearchManagerService 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/177858728): 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()) {
|
||||
// Send the pending sessions over to the service
|
||||
resurrectSessionsLocked();
|
||||
} else {
|
||||
// Clear the remote service for the next call
|
||||
updateRemoteServiceLocked();
|
||||
}
|
||||
}
|
||||
return enabledChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the service of a new cloudsearch session.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
public void onSearchLocked(@NonNull SearchRequest searchRequest,
|
||||
@NonNull ICloudSearchManagerCallback callback) {
|
||||
String filterList = searchRequest.getSearchConstraints().containsKey(
|
||||
SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER)
|
||||
? searchRequest.getSearchConstraints().getString(
|
||||
SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER) : "";
|
||||
|
||||
String remoteServicePackageName = getServiceComponentName().getPackageName();
|
||||
// By default, all providers are marked as wanted.
|
||||
boolean wantedProvider = true;
|
||||
if (filterList.length() > 0) {
|
||||
// If providers are specified by the client,
|
||||
wantedProvider = false;
|
||||
String[] providersSpecified = filterList.split(";");
|
||||
for (int i = 0; i < providersSpecified.length; i++) {
|
||||
if (providersSpecified[i].equals(remoteServicePackageName)) {
|
||||
wantedProvider = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the provider was not requested by the Client, the request will not be sent to the
|
||||
// provider.
|
||||
if (!wantedProvider) {
|
||||
// TODO(216520546) Send a failure callback to the client.
|
||||
return;
|
||||
}
|
||||
final boolean serviceExists = resolveService(searchRequest,
|
||||
s -> s.onSearch(searchRequest));
|
||||
String requestId = searchRequest.getRequestId();
|
||||
if (serviceExists && !mCallbackQueue.containsKey(requestId)) {
|
||||
final CloudSearchCallbackInfo sessionInfo = new CloudSearchCallbackInfo(
|
||||
requestId, searchRequest, callback, callback.asBinder(), () -> {
|
||||
synchronized (mLock) {
|
||||
onDestroyLocked(requestId);
|
||||
}
|
||||
});
|
||||
if (sessionInfo.linkToDeath()) {
|
||||
mCallbackQueue.put(requestId, sessionInfo);
|
||||
} else {
|
||||
// destroy the session if calling process is already dead
|
||||
onDestroyLocked(requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to return results back to the clients.
|
||||
*/
|
||||
public void onReturnResultsLocked(@NonNull IBinder token,
|
||||
@NonNull String requestId,
|
||||
@NonNull SearchResponse response) {
|
||||
if (mCallbackQueue.containsKey(requestId)) {
|
||||
response.setSource(mRemoteService.getComponentName().getPackageName());
|
||||
final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.getElement(requestId);
|
||||
try {
|
||||
if (response.getStatusCode() == SearchResponse.SEARCH_STATUS_OK) {
|
||||
sessionInfo.mCallback.onSearchSucceeded(response);
|
||||
} else {
|
||||
sessionInfo.mCallback.onSearchFailed(response);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
onDestroyLocked(requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the server about the end of an existing cloudsearch session.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
public void onDestroyLocked(@NonNull String requestId) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "onDestroyLocked(): requestId=" + requestId);
|
||||
}
|
||||
final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.removeElement(requestId);
|
||||
sessionInfo.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailureOrTimeout(boolean timedOut) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
|
||||
}
|
||||
// Do nothing, we are just proxying to the cloudsearch service
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectedStateChanged(boolean connected) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
|
||||
}
|
||||
if (connected) {
|
||||
synchronized (mLock) {
|
||||
if (mZombie) {
|
||||
// Validation check - shouldn't happen
|
||||
if (mRemoteService == null) {
|
||||
Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
|
||||
return;
|
||||
}
|
||||
mZombie = false;
|
||||
resurrectSessionsLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDied(RemoteCloudSearchService service) {
|
||||
if (isDebug()) {
|
||||
Slog.w(TAG, "onServiceDied(): service=" + service);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mZombie = true;
|
||||
}
|
||||
updateRemoteServiceLocked();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateRemoteServiceLocked() {
|
||||
if (mRemoteService != null) {
|
||||
mRemoteService.destroy();
|
||||
mRemoteService = null;
|
||||
}
|
||||
}
|
||||
|
||||
void onPackageUpdatedLocked() {
|
||||
if (isDebug()) {
|
||||
Slog.v(TAG, "onPackageUpdatedLocked()");
|
||||
}
|
||||
destroyAndRebindRemoteService();
|
||||
}
|
||||
|
||||
void onPackageRestartedLocked() {
|
||||
if (isDebug()) {
|
||||
Slog.v(TAG, "onPackageRestartedLocked()");
|
||||
}
|
||||
destroyAndRebindRemoteService();
|
||||
}
|
||||
|
||||
private void destroyAndRebindRemoteService() {
|
||||
if (mRemoteService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "Destroying the old remote service.");
|
||||
}
|
||||
mRemoteService.destroy();
|
||||
mRemoteService = null;
|
||||
|
||||
synchronized (mLock) {
|
||||
mZombie = true;
|
||||
}
|
||||
mRemoteService = getRemoteServiceLocked();
|
||||
if (mRemoteService != null) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "Rebinding to the new remote service.");
|
||||
}
|
||||
mRemoteService.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the remote service connected, it's used to restore state from a 'zombie'
|
||||
* service (i.e., after it died).
|
||||
*/
|
||||
private void resurrectSessionsLocked() {
|
||||
final int numCallbacks = mCallbackQueue.size();
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
|
||||
+ numCallbacks + " requests.");
|
||||
}
|
||||
|
||||
for (CloudSearchCallbackInfo callbackInfo : mCallbackQueue.values()) {
|
||||
callbackInfo.resurrectSessionLocked(this, callbackInfo.mToken);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
protected boolean resolveService(
|
||||
@NonNull final SearchRequest requestId,
|
||||
@NonNull final AbstractRemoteService.AsyncRequest<ICloudSearchService> cb) {
|
||||
|
||||
final RemoteCloudSearchService service = getRemoteServiceLocked();
|
||||
if (service != null) {
|
||||
service.executeOnResolvedService(cb);
|
||||
}
|
||||
return service != null;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private RemoteCloudSearchService 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 RemoteCloudSearchService(getContext(),
|
||||
CloudSearchService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
|
||||
mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
|
||||
}
|
||||
|
||||
return mRemoteService;
|
||||
}
|
||||
|
||||
private static final class CloudSearchCallbackInfo {
|
||||
private static final boolean DEBUG = false; // Do not submit with true
|
||||
@NonNull
|
||||
final IBinder mToken;
|
||||
@NonNull
|
||||
final IBinder.DeathRecipient mDeathRecipient;
|
||||
@NonNull
|
||||
private final String mRequestId;
|
||||
@NonNull
|
||||
private final SearchRequest mSearchRequest;
|
||||
private final ICloudSearchManagerCallback mCallback;
|
||||
|
||||
CloudSearchCallbackInfo(
|
||||
@NonNull final String id,
|
||||
@NonNull final SearchRequest request,
|
||||
@NonNull final ICloudSearchManagerCallback callback,
|
||||
@NonNull final IBinder token,
|
||||
@NonNull final IBinder.DeathRecipient deathRecipient) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Creating CloudSearchSessionInfo for session Id=" + id);
|
||||
}
|
||||
mRequestId = id;
|
||||
mSearchRequest = request;
|
||||
mCallback = callback;
|
||||
mToken = token;
|
||||
mDeathRecipient = deathRecipient;
|
||||
}
|
||||
|
||||
boolean linkToDeath() {
|
||||
try {
|
||||
mToken.linkToDeath(mDeathRecipient, 0);
|
||||
} catch (RemoteException e) {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Caller is dead before session can be started, requestId: "
|
||||
+ mRequestId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Removing callback for Request Id=" + mRequestId);
|
||||
}
|
||||
if (mToken != null) {
|
||||
mToken.unlinkToDeath(mDeathRecipient, 0);
|
||||
}
|
||||
mCallback.asBinder().unlinkToDeath(mDeathRecipient, 0);
|
||||
}
|
||||
|
||||
void resurrectSessionLocked(CloudSearchPerUserService service, IBinder token) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
|
||||
+ ") for request Id=" + mRequestId);
|
||||
}
|
||||
service.onSearchLocked(mSearchRequest, mCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 com.android.server.cloudsearch;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.service.cloudsearch.ICloudSearchService;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
|
||||
|
||||
|
||||
/**
|
||||
* Proxy to the {@link android.service.cloudsearch.CloudSearchService} implementation in another
|
||||
* process.
|
||||
*/
|
||||
public class RemoteCloudSearchService extends
|
||||
AbstractMultiplePendingRequestsRemoteService<RemoteCloudSearchService,
|
||||
ICloudSearchService> {
|
||||
|
||||
private static final String TAG = "RemoteCloudSearchService";
|
||||
|
||||
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
|
||||
|
||||
private final RemoteCloudSearchServiceCallbacks mCallback;
|
||||
|
||||
public RemoteCloudSearchService(Context context, String serviceInterface,
|
||||
ComponentName componentName, int userId,
|
||||
RemoteCloudSearchServiceCallbacks callback, boolean bindInstantServiceAllowed,
|
||||
boolean verbose) {
|
||||
super(context, serviceInterface, componentName, userId, callback,
|
||||
context.getMainThreadHandler(),
|
||||
bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
|
||||
verbose, /* initialCapacity= */ 1);
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ICloudSearchService getServiceInterface(IBinder service) {
|
||||
return ICloudSearchService.Stub.asInterface(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getTimeoutIdleBindMillis() {
|
||||
return PERMANENT_BOUND_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getRemoteRequestMillis() {
|
||||
return TIMEOUT_REMOTE_REQUEST_MILLIS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a request to bind to the remote service.
|
||||
*/
|
||||
public void reconnect() {
|
||||
super.scheduleBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule async request on remote service.
|
||||
*/
|
||||
public void scheduleOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) {
|
||||
scheduleAsyncRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute async request on remote service immediately instead of sending it to Handler queue.
|
||||
*/
|
||||
public void executeOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) {
|
||||
executeAsyncRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Failure callback
|
||||
*/
|
||||
public interface RemoteCloudSearchServiceCallbacks
|
||||
extends VultureCallback<RemoteCloudSearchService> {
|
||||
|
||||
/**
|
||||
* Notifies a the failure or timeout of a remote call.
|
||||
*/
|
||||
void onFailureOrTimeout(boolean timedOut);
|
||||
|
||||
/**
|
||||
* Notifies change in connected state of the remote service.
|
||||
*/
|
||||
void onConnectedStateChanged(boolean connected);
|
||||
}
|
||||
|
||||
@Override // from AbstractRemoteService
|
||||
protected void handleOnConnectedStateChanged(boolean connected) {
|
||||
if (mCallback != null) {
|
||||
mCallback.onConnectedStateChanged(connected);
|
||||
}
|
||||
}
|
||||
}
|
||||
99
services/core/java/com/android/server/CircularQueue.java
Normal file
99
services/core/java/com/android/server/CircularQueue.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 com.android.server;
|
||||
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* CircularQueue of length limit which puts keys in a circular LinkedList and values in an ArrayMap.
|
||||
* @param <K> key
|
||||
* @param <V> value
|
||||
*/
|
||||
public class CircularQueue<K, V> extends LinkedList<K> {
|
||||
private final int mLimit;
|
||||
private final ArrayMap<K, V> mArrayMap = new ArrayMap<>();
|
||||
|
||||
public CircularQueue(int limit) {
|
||||
this.mLimit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(K k) throws IllegalArgumentException {
|
||||
throw new IllegalArgumentException("Call of add(key) prohibited. Please call put(key, "
|
||||
+ "value) instead. ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a (key|value) pair in the CircularQueue. Only the key will be added to the queue. Value
|
||||
* will be added to the ArrayMap.
|
||||
* @return {@code true} (as specified by {@link Collection#add})
|
||||
*/
|
||||
public boolean put(K key, V value) {
|
||||
super.add(key);
|
||||
mArrayMap.put(key, value);
|
||||
while (size() > mLimit) {
|
||||
K removedKey = super.remove();
|
||||
mArrayMap.remove(removedKey);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element for the provided key from the data structure.
|
||||
* @param key which should be removed
|
||||
* @return the value which was removed
|
||||
*/
|
||||
public V removeElement(K key) {
|
||||
super.remove(key);
|
||||
return mArrayMap.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from the array.
|
||||
* @param key The key of the value to retrieve.
|
||||
* @return Returns the value associated with the given key,
|
||||
* or null if there is no such key.
|
||||
*/
|
||||
public V getElement(K key) {
|
||||
return mArrayMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a key exists in the array.
|
||||
*
|
||||
* @param key The key to search for.
|
||||
* @return Returns true if the key exists, else false.
|
||||
*/
|
||||
public boolean containsKey(K key) {
|
||||
return mArrayMap.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link java.util.Collection} for iterating over and interacting with all values
|
||||
* in the array map.
|
||||
*
|
||||
* <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
|
||||
* requires generating a number of temporary objects and allocates additional state
|
||||
* information associated with the container that will remain for the life of the container.</p>
|
||||
*/
|
||||
public Collection<V> values() {
|
||||
return mArrayMap.values();
|
||||
}
|
||||
}
|
||||
@@ -376,6 +376,8 @@ public final class SystemServer implements Dumpable {
|
||||
"com.android.server.searchui.SearchUiManagerService";
|
||||
private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.smartspace.SmartspaceManagerService";
|
||||
private static final String CLOUDSEARCH_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.cloudsearch.CloudSearchManagerService";
|
||||
private static final String DEVICE_IDLE_CONTROLLER_CLASS =
|
||||
"com.android.server.DeviceIdleController";
|
||||
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
|
||||
@@ -1853,6 +1855,12 @@ public final class SystemServer implements Dumpable {
|
||||
mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
|
||||
t.traceEnd();
|
||||
|
||||
// CloudSearch manager service
|
||||
// TODO: add deviceHasConfigString(context, R.string.config_defaultCloudSearchServices)
|
||||
t.traceBegin("StartCloudSearchService");
|
||||
mSystemServiceManager.startService(CLOUDSEARCH_MANAGER_SERVICE_CLASS);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("InitConnectivityModuleConnector");
|
||||
try {
|
||||
ConnectivityModuleConnector.getInstance().init(context);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 com.android.server;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test {@link CircularQueue}.
|
||||
*/
|
||||
public class CircularQueueTest {
|
||||
|
||||
private CircularQueue<Integer, String> mQueue;
|
||||
private static final int LIMIT = 2;
|
||||
|
||||
@Test
|
||||
public void testQueueInsertionAndDeletion() {
|
||||
mQueue = new CircularQueue<>(LIMIT);
|
||||
mQueue.put(1, "A");
|
||||
assertEquals(mQueue.getElement(1), "A");
|
||||
mQueue.removeElement(1);
|
||||
assertNull(mQueue.getElement(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueueLimit() {
|
||||
mQueue = new CircularQueue<>(LIMIT);
|
||||
mQueue.put(1, "A");
|
||||
mQueue.put(2, "B");
|
||||
mQueue.put(3, "C");
|
||||
assertNull(mQueue.getElement(1));
|
||||
assertEquals(mQueue.getElement(2), "B");
|
||||
assertEquals(mQueue.getElement(3), "C");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueueElements() {
|
||||
mQueue = new CircularQueue<>(LIMIT);
|
||||
mQueue.put(1, "A");
|
||||
mQueue.put(2, "B");
|
||||
assertEquals(mQueue.values().size(), 2);
|
||||
mQueue.put(3, "C");
|
||||
assertEquals(mQueue.values().size(), 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user