Merge "Add the skeleton code for People Service which is a new system service with only internal APIs"
This commit is contained in:
@@ -61,6 +61,7 @@ java_library {
|
||||
"services.devicepolicy",
|
||||
"services.midi",
|
||||
"services.net",
|
||||
"services.people",
|
||||
"services.print",
|
||||
"services.restrictions",
|
||||
"services.startop",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.people;
|
||||
|
||||
import android.service.appprediction.IPredictionService;
|
||||
|
||||
/**
|
||||
* @hide Only for use within the system server.
|
||||
*/
|
||||
public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
|
||||
@@ -126,6 +126,7 @@ import com.android.server.om.OverlayManagerService;
|
||||
import com.android.server.os.BugreportManagerService;
|
||||
import com.android.server.os.DeviceIdentifiersPolicyService;
|
||||
import com.android.server.os.SchedulingPolicyService;
|
||||
import com.android.server.people.PeopleService;
|
||||
import com.android.server.pm.BackgroundDexOptService;
|
||||
import com.android.server.pm.CrossProfileAppsService;
|
||||
import com.android.server.pm.DataLoaderManagerService;
|
||||
@@ -1917,6 +1918,10 @@ public final class SystemServer {
|
||||
t.traceBegin("StartCrossProfileAppsService");
|
||||
mSystemServiceManager.startService(CrossProfileAppsService.class);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("StartPeopleService");
|
||||
mSystemServiceManager.startService(PeopleService.class);
|
||||
t.traceEnd();
|
||||
}
|
||||
|
||||
if (!isWatch) {
|
||||
|
||||
5
services/people/Android.bp
Normal file
5
services/people/Android.bp
Normal file
@@ -0,0 +1,5 @@
|
||||
java_library_static {
|
||||
name: "services.people",
|
||||
srcs: ["java/**/*.java"],
|
||||
libs: ["services.core"],
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.people;
|
||||
|
||||
import android.app.prediction.AppPredictionContext;
|
||||
import android.app.prediction.AppPredictionSessionId;
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.app.prediction.IPredictionCallback;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A service that manages the people and conversations provided by apps.
|
||||
*/
|
||||
public class PeopleService extends SystemService {
|
||||
|
||||
private static final String TAG = "PeopleService";
|
||||
|
||||
/**
|
||||
* Initializes the system service.
|
||||
*
|
||||
* @param context The system server context.
|
||||
*/
|
||||
public PeopleService(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishLocalService(PeopleServiceInternal.class, new LocalService());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
final class LocalService extends PeopleServiceInternal {
|
||||
|
||||
private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
|
||||
|
||||
@Override
|
||||
public void onCreatePredictionSession(AppPredictionContext context,
|
||||
AppPredictionSessionId sessionId) {
|
||||
mSessions.put(sessionId, new SessionInfo(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
|
||||
runForSession(sessionId,
|
||||
sessionInfo -> sessionInfo.getPredictor().onAppTargetEvent(event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyLaunchLocationShown(AppPredictionSessionId sessionId,
|
||||
String launchLocation, ParceledListSlice targetIds) {
|
||||
runForSession(sessionId,
|
||||
sessionInfo -> sessionInfo.getPredictor().onLaunchLocationShown(
|
||||
launchLocation, targetIds.getList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets,
|
||||
IPredictionCallback callback) {
|
||||
runForSession(sessionId,
|
||||
sessionInfo -> sessionInfo.getPredictor().onSortAppTargets(
|
||||
targets.getList(),
|
||||
targetList -> invokePredictionCallback(callback, targetList)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPredictionUpdates(AppPredictionSessionId sessionId,
|
||||
IPredictionCallback callback) {
|
||||
runForSession(sessionId, sessionInfo -> sessionInfo.addCallback(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterPredictionUpdates(AppPredictionSessionId sessionId,
|
||||
IPredictionCallback callback) {
|
||||
runForSession(sessionId, sessionInfo -> sessionInfo.removeCallback(callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPredictionUpdate(AppPredictionSessionId sessionId) {
|
||||
runForSession(sessionId,
|
||||
sessionInfo -> sessionInfo.getPredictor().onRequestPredictionUpdate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
|
||||
runForSession(sessionId, sessionInfo -> {
|
||||
sessionInfo.onDestroy();
|
||||
mSessions.remove(sessionId);
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
|
||||
return mSessions.get(sessionId);
|
||||
}
|
||||
|
||||
private void runForSession(AppPredictionSessionId sessionId, Consumer<SessionInfo> method) {
|
||||
SessionInfo sessionInfo = mSessions.get(sessionId);
|
||||
if (sessionInfo == null) {
|
||||
Slog.e(TAG, "Failed to find the session: " + sessionId);
|
||||
return;
|
||||
}
|
||||
method.accept(sessionInfo);
|
||||
}
|
||||
|
||||
private void invokePredictionCallback(IPredictionCallback callback,
|
||||
List<AppTarget> targets) {
|
||||
try {
|
||||
callback.onResult(new ParceledListSlice<>(targets));
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to calling callback" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.people;
|
||||
|
||||
import android.app.prediction.AppPredictionContext;
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.IPredictionCallback;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.people.prediction.ConversationPredictor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Manages the information and callbacks in an app prediction request session. */
|
||||
class SessionInfo {
|
||||
|
||||
private static final String TAG = "SessionInfo";
|
||||
|
||||
private final ConversationPredictor mConversationPredictor;
|
||||
private final RemoteCallbackList<IPredictionCallback> mCallbacks =
|
||||
new RemoteCallbackList<>();
|
||||
|
||||
SessionInfo(AppPredictionContext predictionContext) {
|
||||
mConversationPredictor = new ConversationPredictor(predictionContext,
|
||||
this::updatePredictions);
|
||||
}
|
||||
|
||||
void addCallback(IPredictionCallback callback) {
|
||||
mCallbacks.register(callback);
|
||||
}
|
||||
|
||||
void removeCallback(IPredictionCallback callback) {
|
||||
mCallbacks.unregister(callback);
|
||||
}
|
||||
|
||||
ConversationPredictor getPredictor() {
|
||||
return mConversationPredictor;
|
||||
}
|
||||
|
||||
void onDestroy() {
|
||||
mCallbacks.kill();
|
||||
}
|
||||
|
||||
private void updatePredictions(List<AppTarget> targets) {
|
||||
int callbackCount = mCallbacks.beginBroadcast();
|
||||
for (int i = 0; i < callbackCount; i++) {
|
||||
try {
|
||||
mCallbacks.getBroadcastItem(i).onResult(new ParceledListSlice<>(targets));
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to calling callback" + e);
|
||||
}
|
||||
}
|
||||
mCallbacks.finishBroadcast();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.people.prediction;
|
||||
|
||||
import android.annotation.MainThread;
|
||||
import android.app.prediction.AppPredictionContext;
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.app.prediction.AppTargetId;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Predictor that predicts the conversations or apps the user is most likely to open.
|
||||
*/
|
||||
public class ConversationPredictor {
|
||||
|
||||
private final AppPredictionContext mPredictionContext;
|
||||
private final Consumer<List<AppTarget>> mUpdatePredictionsMethod;
|
||||
private final ExecutorService mCallbackExecutor;
|
||||
|
||||
public ConversationPredictor(AppPredictionContext predictionContext,
|
||||
Consumer<List<AppTarget>> updatePredictionsMethod) {
|
||||
mPredictionContext = predictionContext;
|
||||
mUpdatePredictionsMethod = updatePredictionsMethod;
|
||||
mCallbackExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the client app to indicate a target launch.
|
||||
*/
|
||||
@MainThread
|
||||
public void onAppTargetEvent(AppTargetEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the client app to indicate a particular location has been shown to the user.
|
||||
*/
|
||||
@MainThread
|
||||
public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the client app to request sorting of the provided targets based on the prediction
|
||||
* ranking.
|
||||
*/
|
||||
@MainThread
|
||||
public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
|
||||
mCallbackExecutor.execute(() -> callback.accept(targets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the client app to request target predictions.
|
||||
*/
|
||||
@MainThread
|
||||
public void onRequestPredictionUpdate() {
|
||||
List<AppTarget> targets = new ArrayList<>();
|
||||
mCallbackExecutor.execute(() -> mUpdatePredictionsMethod.accept(targets));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Consumer<List<AppTarget>> getUpdatePredictionsMethod() {
|
||||
return mUpdatePredictionsMethod;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ android_test {
|
||||
"services.core",
|
||||
"services.devicepolicy",
|
||||
"services.net",
|
||||
"services.people",
|
||||
"services.usage",
|
||||
"guava",
|
||||
"androidx.test.core",
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.people;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.prediction.AppPredictionContext;
|
||||
import android.app.prediction.AppPredictionSessionId;
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.IPredictionCallback;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.Binder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class PeopleServiceTest {
|
||||
|
||||
private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
|
||||
private static final int APP_PREDICTION_TARGET_COUNT = 4;
|
||||
private static final String TEST_PACKAGE_NAME = "com.example";
|
||||
|
||||
private PeopleServiceInternal mServiceInternal;
|
||||
private PeopleService.LocalService mLocalService;
|
||||
private AppPredictionSessionId mSessionId;
|
||||
private AppPredictionContext mPredictionContext;
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private IPredictionCallback mCallback;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
|
||||
when(mCallback.asBinder()).thenReturn(new Binder());
|
||||
|
||||
PeopleService service = new PeopleService(mContext);
|
||||
service.onStart();
|
||||
|
||||
mServiceInternal = LocalServices.getService(PeopleServiceInternal.class);
|
||||
mLocalService = (PeopleService.LocalService) mServiceInternal;
|
||||
|
||||
mSessionId = new AppPredictionSessionId("abc");
|
||||
mPredictionContext = new AppPredictionContext.Builder(mContext)
|
||||
.setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
|
||||
.setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
|
||||
.build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
LocalServices.removeServiceForTest(PeopleServiceInternal.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterCallbacks() throws RemoteException {
|
||||
mServiceInternal.onCreatePredictionSession(mPredictionContext, mSessionId);
|
||||
|
||||
SessionInfo sessionInfo = mLocalService.getSessionInfo(mSessionId);
|
||||
|
||||
mServiceInternal.registerPredictionUpdates(mSessionId, mCallback);
|
||||
|
||||
Consumer<List<AppTarget>> updatePredictionMethod =
|
||||
sessionInfo.getPredictor().getUpdatePredictionsMethod();
|
||||
updatePredictionMethod.accept(new ArrayList<>());
|
||||
updatePredictionMethod.accept(new ArrayList<>());
|
||||
|
||||
verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
|
||||
|
||||
mServiceInternal.unregisterPredictionUpdates(mSessionId, mCallback);
|
||||
|
||||
updatePredictionMethod.accept(new ArrayList<>());
|
||||
|
||||
// After the un-registration, the callback should no longer be called.
|
||||
verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
|
||||
|
||||
mServiceInternal.onDestroyPredictionSession(mSessionId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user