Merge "Add the skeleton code for People Service which is a new system service with only internal APIs"

This commit is contained in:
Danning Chen
2020-01-07 22:10:19 +00:00
committed by Android (Google) Code Review
9 changed files with 445 additions and 0 deletions

View File

@@ -61,6 +61,7 @@ java_library {
"services.devicepolicy",
"services.midi",
"services.net",
"services.people",
"services.print",
"services.restrictions",
"services.startop",

View File

@@ -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 {}

View File

@@ -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) {

View File

@@ -0,0 +1,5 @@
java_library_static {
name: "services.people",
srcs: ["java/**/*.java"],
libs: ["services.core"],
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -26,6 +26,7 @@ android_test {
"services.core",
"services.devicepolicy",
"services.net",
"services.people",
"services.usage",
"guava",
"androidx.test.core",

View File

@@ -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);
}
}