Merge "Add listener for conversation changes" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
bf07abc3aa
33
core/java/android/app/people/IConversationListener.aidl
Normal file
33
core/java/android/app/people/IConversationListener.aidl
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.people;
|
||||
|
||||
import android.app.people.ConversationChannel;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for PeopleManager#ConversationListener.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IConversationListener
|
||||
{
|
||||
void onConversationUpdate(in ConversationChannel conversation);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package android.app.people;
|
||||
|
||||
import android.app.people.ConversationStatus;
|
||||
import android.app.people.ConversationChannel;
|
||||
import android.app.people.IConversationListener;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
@@ -62,4 +63,6 @@ interface IPeopleManager {
|
||||
void clearStatus(in String packageName, int userId, in String conversationId, in String statusId);
|
||||
void clearStatuses(in String packageName, int userId, in String conversationId);
|
||||
ParceledListSlice getStatuses(in String packageName, int userId, in String conversationId);
|
||||
void registerConversationListener(in String packageName, int userId, in String shortcutId, in IConversationListener callback);
|
||||
void unregisterConversationListener(in IConversationListener callback);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.app.people;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
@@ -25,12 +27,18 @@ import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* This class allows interaction with conversation and people data.
|
||||
@@ -40,11 +48,18 @@ public final class PeopleManager {
|
||||
|
||||
private static final String LOG_TAG = PeopleManager.class.getSimpleName();
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public Map<ConversationListener, Pair<Executor, IConversationListener>>
|
||||
mConversationListeners = new HashMap<>();
|
||||
|
||||
@NonNull
|
||||
private final IPeopleManager mService;
|
||||
private Context mContext;
|
||||
|
||||
@NonNull
|
||||
private IPeopleManager mService;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
@@ -55,6 +70,15 @@ public final class PeopleManager {
|
||||
Context.PEOPLE_SERVICE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public PeopleManager(@NonNull Context context, IPeopleManager service) {
|
||||
mContext = context;
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a shortcut has a conversation associated.
|
||||
*
|
||||
@@ -66,9 +90,8 @@ public final class PeopleManager {
|
||||
* clients.
|
||||
*
|
||||
* @param packageName name of the package the conversation is part of
|
||||
* @param shortcutId the shortcut id backing the conversation
|
||||
* @param shortcutId the shortcut id backing the conversation
|
||||
* @return whether the {@shortcutId} is backed by a Conversation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@@ -94,8 +117,7 @@ public final class PeopleManager {
|
||||
*
|
||||
* @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the
|
||||
* conversation that has an active status
|
||||
* @param status the current status for the given conversation
|
||||
*
|
||||
* @param status the current status for the given conversation
|
||||
* @return whether the role is available in the system
|
||||
*/
|
||||
public void addOrUpdateStatus(@NonNull String conversationId,
|
||||
@@ -115,8 +137,8 @@ public final class PeopleManager {
|
||||
*
|
||||
* @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the
|
||||
* conversation that has an active status
|
||||
* @param statusId the {@link ConversationStatus#getId() id} of a published status for the given
|
||||
* conversation
|
||||
* @param statusId the {@link ConversationStatus#getId() id} of a published status for the
|
||||
* given conversation
|
||||
*/
|
||||
public void clearStatus(@NonNull String conversationId, @NonNull String statusId) {
|
||||
Preconditions.checkStringNotEmpty(conversationId);
|
||||
@@ -155,7 +177,7 @@ public final class PeopleManager {
|
||||
try {
|
||||
final ParceledListSlice<ConversationStatus> parceledList
|
||||
= mService.getStatuses(
|
||||
mContext.getPackageName(), mContext.getUserId(), conversationId);
|
||||
mContext.getPackageName(), mContext.getUserId(), conversationId);
|
||||
if (parceledList != null) {
|
||||
return parceledList.getList();
|
||||
}
|
||||
@@ -164,4 +186,103 @@ public final class PeopleManager {
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listeners for conversation changes.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface ConversationListener {
|
||||
/**
|
||||
* Triggers when the conversation registered for a listener has been updated.
|
||||
*
|
||||
* @param conversation The conversation with modified data
|
||||
* @see IPeopleManager#registerConversationListener(String, int, String,
|
||||
* android.app.people.ConversationListener)
|
||||
*
|
||||
* <p>Only system root and SysUI have access to register the listener.
|
||||
*/
|
||||
default void onConversationUpdate(@NonNull ConversationChannel conversation) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener to watch for changes to the conversation identified by {@code
|
||||
* packageName}, {@code userId}, and {@code shortcutId}.
|
||||
*
|
||||
* @param packageName The package name to match and filter the conversation to send updates for.
|
||||
* @param userId The user ID to match and filter the conversation to send updates for.
|
||||
* @param shortcutId The shortcut ID to match and filter the conversation to send updates for.
|
||||
* @param listener The listener to register to receive conversation updates.
|
||||
* @param executor {@link Executor} to handle the listeners. To dispatch listeners to the
|
||||
* main thread of your application, you can use
|
||||
* {@link android.content.Context#getMainExecutor()}.
|
||||
* @hide
|
||||
*/
|
||||
public void registerConversationListener(String packageName, int userId, String shortcutId,
|
||||
ConversationListener listener, Executor executor) {
|
||||
requireNonNull(listener, "Listener cannot be null");
|
||||
requireNonNull(packageName, "Package name cannot be null");
|
||||
requireNonNull(shortcutId, "Shortcut ID cannot be null");
|
||||
synchronized (mConversationListeners) {
|
||||
IConversationListener proxy = (IConversationListener) new ConversationListenerProxy(
|
||||
executor, listener);
|
||||
try {
|
||||
mService.registerConversationListener(
|
||||
packageName, userId, shortcutId, proxy);
|
||||
mConversationListeners.put(listener,
|
||||
new Pair<>(executor, proxy));
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the listener previously registered to watch conversation changes.
|
||||
*
|
||||
* @param listener The listener to register to receive conversation updates.
|
||||
* @hide
|
||||
*/
|
||||
public void unregisterConversationListener(
|
||||
ConversationListener listener) {
|
||||
requireNonNull(listener, "Listener cannot be null");
|
||||
|
||||
synchronized (mConversationListeners) {
|
||||
if (mConversationListeners.containsKey(listener)) {
|
||||
IConversationListener proxy = mConversationListeners.remove(listener).second;
|
||||
try {
|
||||
mService.unregisterConversationListener(proxy);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener proxy class for {@link ConversationListener}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private static class ConversationListenerProxy extends
|
||||
IConversationListener.Stub {
|
||||
private final Executor mExecutor;
|
||||
private final ConversationListener mListener;
|
||||
|
||||
ConversationListenerProxy(Executor executor, ConversationListener listener) {
|
||||
mExecutor = executor;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationUpdate(@NonNull ConversationChannel conversation) {
|
||||
if (mListener == null || mExecutor == null) {
|
||||
// Binder is dead.
|
||||
Slog.e(LOG_TAG, "Binder is dead");
|
||||
return;
|
||||
}
|
||||
mExecutor.execute(() -> mListener.onConversationUpdate(conversation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.people;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.test.TestLooper;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Tests for {@link android.app.people.PeopleManager.ConversationListener} and relevant APIs.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PeopleManagerTest {
|
||||
|
||||
private static final String CONVERSATION_ID_1 = "12";
|
||||
private static final String CONVERSATION_ID_2 = "123";
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private final TestLooper mTestLooper = new TestLooper();
|
||||
|
||||
@Mock
|
||||
private IPeopleManager mService;
|
||||
private PeopleManager mPeopleManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mContext = InstrumentationRegistry.getContext();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mPeopleManager = new PeopleManager(mContext, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectlyMapsToProxyConversationListener() throws Exception {
|
||||
PeopleManager.ConversationListener listenerForConversation1 = mock(
|
||||
PeopleManager.ConversationListener.class);
|
||||
registerListener(CONVERSATION_ID_1, listenerForConversation1);
|
||||
PeopleManager.ConversationListener listenerForConversation2 = mock(
|
||||
PeopleManager.ConversationListener.class);
|
||||
registerListener(CONVERSATION_ID_2, listenerForConversation2);
|
||||
|
||||
Map<PeopleManager.ConversationListener, Pair<Executor, IConversationListener>>
|
||||
listenersToProxy =
|
||||
mPeopleManager.mConversationListeners;
|
||||
Pair<Executor, IConversationListener> listener = listenersToProxy.get(
|
||||
listenerForConversation1);
|
||||
ConversationChannel conversation = getConversation(CONVERSATION_ID_1);
|
||||
listener.second.onConversationUpdate(getConversation(CONVERSATION_ID_1));
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Only call the associated listener.
|
||||
verify(listenerForConversation2, never()).onConversationUpdate(any());
|
||||
// Should update the listeners mapped to the proxy.
|
||||
ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass(
|
||||
ConversationChannel.class);
|
||||
verify(listenerForConversation1, times(1)).onConversationUpdate(
|
||||
capturedConversation.capture());
|
||||
ConversationChannel conversationChannel = capturedConversation.getValue();
|
||||
assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1);
|
||||
assertEquals(conversationChannel.getShortcutInfo().getLabel(),
|
||||
conversation.getShortcutInfo().getLabel());
|
||||
}
|
||||
|
||||
private ConversationChannel getConversation(String shortcutId) {
|
||||
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext,
|
||||
shortcutId).setLongLabel(
|
||||
"name").build();
|
||||
NotificationChannel notificationChannel = new NotificationChannel("123",
|
||||
"channel",
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
return new ConversationChannel(shortcutInfo, 0,
|
||||
notificationChannel, null,
|
||||
123L, false);
|
||||
}
|
||||
|
||||
private void registerListener(String conversationId,
|
||||
PeopleManager.ConversationListener listener) {
|
||||
mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(),
|
||||
conversationId, listener,
|
||||
mTestLooper.getNewExecutor());
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.people.ConversationChannel;
|
||||
import android.app.people.ConversationStatus;
|
||||
import android.app.people.IConversationListener;
|
||||
import android.app.people.IPeopleManager;
|
||||
import android.app.prediction.AppPredictionContext;
|
||||
import android.app.prediction.AppPredictionSessionId;
|
||||
@@ -33,10 +34,12 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
@@ -47,6 +50,7 @@ import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.people.data.DataManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
@@ -58,7 +62,9 @@ public class PeopleService extends SystemService {
|
||||
|
||||
private static final String TAG = "PeopleService";
|
||||
|
||||
private final DataManager mDataManager;
|
||||
private DataManager mDataManager;
|
||||
@VisibleForTesting
|
||||
ConversationListenerHelper mConversationListenerHelper;
|
||||
|
||||
private PackageManagerInternal mPackageManagerInternal;
|
||||
|
||||
@@ -71,6 +77,8 @@ public class PeopleService extends SystemService {
|
||||
super(context);
|
||||
|
||||
mDataManager = new DataManager(context);
|
||||
mConversationListenerHelper = new ConversationListenerHelper();
|
||||
mDataManager.addConversationsListener(mConversationListenerHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,12 +156,14 @@ public class PeopleService extends SystemService {
|
||||
* @param message used as message if SecurityException is thrown
|
||||
* @throws SecurityException if the caller is not system or root
|
||||
*/
|
||||
private static void enforceSystemRootOrSystemUI(Context context, String message) {
|
||||
@VisibleForTesting
|
||||
protected void enforceSystemRootOrSystemUI(Context context, String message) {
|
||||
if (isSystemOrRoot()) return;
|
||||
context.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
|
||||
message);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
final IBinder mService = new IPeopleManager.Stub() {
|
||||
|
||||
@Override
|
||||
@@ -241,8 +251,137 @@ public class PeopleService extends SystemService {
|
||||
return new ParceledListSlice<>(
|
||||
mDataManager.getStatuses(packageName, userId, conversationId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerConversationListener(
|
||||
String packageName, int userId, String shortcutId, IConversationListener listener) {
|
||||
enforceSystemRootOrSystemUI(getContext(), "register conversation listener");
|
||||
mConversationListenerHelper.addConversationListener(
|
||||
new ListenerKey(packageName, userId, shortcutId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterConversationListener(IConversationListener listener) {
|
||||
enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener");
|
||||
mConversationListenerHelper.removeConversationListener(listener);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listeners for conversation changes.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface ConversationsListener {
|
||||
/**
|
||||
* Triggers with the list of modified conversations from {@link DataManager} for dispatching
|
||||
* relevant updates to clients.
|
||||
*
|
||||
* @param conversations The conversations with modified data
|
||||
* @see IPeopleManager#registerConversationListener(String, int, String,
|
||||
* android.app.people.ConversationListener)
|
||||
*/
|
||||
default void onConversationsUpdate(@NonNull List<ConversationChannel> conversations) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code ConversationListenerHelper} to dispatch conversation updates to registered
|
||||
* clients.
|
||||
*/
|
||||
public static class ConversationListenerHelper implements ConversationsListener {
|
||||
|
||||
ConversationListenerHelper() {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
final RemoteCallbackList<IConversationListener> mListeners =
|
||||
new RemoteCallbackList<>();
|
||||
|
||||
/** Adds {@code listener} with {@code key} associated. */
|
||||
public synchronized void addConversationListener(ListenerKey key,
|
||||
IConversationListener listener) {
|
||||
mListeners.unregister(listener);
|
||||
mListeners.register(listener, key);
|
||||
}
|
||||
|
||||
/** Removes {@code listener}. */
|
||||
public synchronized void removeConversationListener(
|
||||
IConversationListener listener) {
|
||||
mListeners.unregister(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
/** Dispatches updates to {@code mListeners} with keys mapped to {@code conversations}. */
|
||||
public void onConversationsUpdate(List<ConversationChannel> conversations) {
|
||||
int count = mListeners.beginBroadcast();
|
||||
// Early opt-out if no listeners are registered.
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
Map<ListenerKey, ConversationChannel> keyedConversations = new HashMap<>();
|
||||
for (ConversationChannel conversation : conversations) {
|
||||
keyedConversations.put(getListenerKey(conversation), conversation);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
final ListenerKey listenerKey = (ListenerKey) mListeners.getBroadcastCookie(i);
|
||||
if (!keyedConversations.containsKey(listenerKey)) {
|
||||
continue;
|
||||
}
|
||||
final IConversationListener listener = mListeners.getBroadcastItem(i);
|
||||
try {
|
||||
ConversationChannel channel = keyedConversations.get(listenerKey);
|
||||
listener.onConversationUpdate(channel);
|
||||
} catch (RemoteException e) {
|
||||
// The RemoteCallbackList will take care of removing the dead object.
|
||||
}
|
||||
}
|
||||
mListeners.finishBroadcast();
|
||||
}
|
||||
|
||||
private ListenerKey getListenerKey(ConversationChannel conversation) {
|
||||
ShortcutInfo info = conversation.getShortcutInfo();
|
||||
return new ListenerKey(info.getPackage(), info.getUserId(),
|
||||
info.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListenerKey {
|
||||
private final String mPackageName;
|
||||
private final Integer mUserId;
|
||||
private final String mShortcutId;
|
||||
|
||||
ListenerKey(String packageName, Integer userId, String shortcutId) {
|
||||
this.mPackageName = packageName;
|
||||
this.mUserId = userId;
|
||||
this.mShortcutId = shortcutId;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public Integer getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
public String getShortcutId() {
|
||||
return mShortcutId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
ListenerKey key = (ListenerKey) o;
|
||||
return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId
|
||||
&& key.getShortcutId().equals(mShortcutId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mPackageName.hashCode() + mUserId.hashCode() + mShortcutId.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
final class LocalService extends PeopleServiceInternal {
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
@@ -75,14 +76,17 @@ import com.android.internal.telephony.SmsApplication;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.notification.NotificationManagerInternal;
|
||||
import com.android.server.notification.ShortcutHelper;
|
||||
import com.android.server.people.PeopleService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -118,6 +122,11 @@ public class DataManager {
|
||||
private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
|
||||
private final SparseArray<NotificationListener> mNotificationListeners = new SparseArray<>();
|
||||
private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
|
||||
@GuardedBy("mLock")
|
||||
private final List<PeopleService.ConversationsListener> mConversationsListeners =
|
||||
new ArrayList<>(1);
|
||||
private final Handler mHandler;
|
||||
|
||||
private ContentObserver mCallLogContentObserver;
|
||||
private ContentObserver mMmsSmsContentObserver;
|
||||
|
||||
@@ -128,14 +137,14 @@ public class DataManager {
|
||||
private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
|
||||
|
||||
public DataManager(Context context) {
|
||||
this(context, new Injector());
|
||||
this(context, new Injector(), BackgroundThread.get().getLooper());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
DataManager(Context context, Injector injector) {
|
||||
DataManager(Context context, Injector injector, Looper looper) {
|
||||
mContext = context;
|
||||
mInjector = injector;
|
||||
mScheduledExecutor = mInjector.createScheduledExecutor();
|
||||
mHandler = new Handler(looper);
|
||||
}
|
||||
|
||||
/** Initialization. Called when the system services are up running. */
|
||||
@@ -234,20 +243,19 @@ public class DataManager {
|
||||
PackageData packageData = userData.getPackageData(packageName);
|
||||
// App may have been uninstalled.
|
||||
if (packageData != null) {
|
||||
return getConversationChannel(packageData, shortcutId);
|
||||
ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
|
||||
return getConversationChannel(packageName, userId, shortcutId, conversationInfo);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) {
|
||||
ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
|
||||
private ConversationChannel getConversationChannel(String packageName, int userId,
|
||||
String shortcutId, ConversationInfo conversationInfo) {
|
||||
if (conversationInfo == null) {
|
||||
return null;
|
||||
}
|
||||
int userId = packageData.getUserId();
|
||||
String packageName = packageData.getPackageName();
|
||||
ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
|
||||
if (shortcutInfo == null) {
|
||||
return null;
|
||||
@@ -278,7 +286,8 @@ public class DataManager {
|
||||
return;
|
||||
}
|
||||
String shortcutId = conversationInfo.getShortcutId();
|
||||
ConversationChannel channel = getConversationChannel(packageData, shortcutId);
|
||||
ConversationChannel channel = getConversationChannel(packageData.getPackageName(),
|
||||
packageData.getUserId(), shortcutId, conversationInfo);
|
||||
if (channel == null || channel.getParentNotificationChannel() == null) {
|
||||
return;
|
||||
}
|
||||
@@ -384,7 +393,11 @@ public class DataManager {
|
||||
ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId);
|
||||
ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify);
|
||||
builder.addOrUpdateStatus(status);
|
||||
cs.addOrUpdate(builder.build());
|
||||
ConversationInfo modifiedConv = builder.build();
|
||||
cs.addOrUpdate(modifiedConv);
|
||||
ConversationChannel conversation = getConversationChannel(packageName, userId,
|
||||
conversationId, modifiedConv);
|
||||
notifyConversationsListeners(Arrays.asList(conversation));
|
||||
|
||||
if (status.getEndTimeMillis() >= 0) {
|
||||
mStatusExpReceiver.scheduleExpiration(
|
||||
@@ -1235,6 +1248,32 @@ public class DataManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds {@code listener} to be notified on conversation changes. */
|
||||
public void addConversationsListener(
|
||||
@NonNull PeopleService.ConversationsListener listener) {
|
||||
synchronized (mConversationsListeners) {
|
||||
mConversationsListeners.add(Objects.requireNonNull(listener));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/178792356): Trigger ConversationsListener on all-related data changes.
|
||||
@VisibleForTesting
|
||||
void notifyConversationsListeners(
|
||||
@Nullable final List<ConversationChannel> changedConversations) {
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
final List<PeopleService.ConversationsListener> copy;
|
||||
synchronized (mLock) {
|
||||
copy = new ArrayList<>(mConversationsListeners);
|
||||
}
|
||||
for (PeopleService.ConversationsListener listener : copy) {
|
||||
listener.onConversationsUpdate(changedConversations);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** A {@link BroadcastReceiver} that receives the intents for a specified user. */
|
||||
private class PerUserBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
|
||||
@@ -16,58 +16,104 @@
|
||||
|
||||
package com.android.server.people;
|
||||
|
||||
import static android.app.people.ConversationStatus.ACTIVITY_GAME;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.people.ConversationChannel;
|
||||
import android.app.people.ConversationStatus;
|
||||
import android.app.people.IConversationListener;
|
||||
import android.app.people.IPeopleManager;
|
||||
import android.app.people.PeopleManager;
|
||||
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.content.pm.ShortcutInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.test.TestLooper;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableContext;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
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 static final int USER_ID = 0;
|
||||
private static final String CONVERSATION_ID_1 = "12";
|
||||
private static final String CONVERSATION_ID_2 = "123";
|
||||
|
||||
private PeopleServiceInternal mServiceInternal;
|
||||
private PeopleService.LocalService mLocalService;
|
||||
private AppPredictionSessionId mSessionId;
|
||||
private AppPredictionContext mPredictionContext;
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private IPredictionCallback mCallback;
|
||||
@Mock
|
||||
private Context mMockContext;
|
||||
|
||||
@Rule
|
||||
public final TestableContext mContext =
|
||||
new TestableContext(InstrumentationRegistry.getContext(), null);
|
||||
|
||||
protected TestableContext getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Mock
|
||||
private IPredictionCallback mCallback;
|
||||
private TestableLooper mTestableLooper;
|
||||
private final TestLooper mTestLooper = new TestLooper();
|
||||
|
||||
private TestablePeopleService mPeopleService;
|
||||
private IPeopleManager mIPeopleManager;
|
||||
private PeopleManager mPeopleManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
|
||||
mPeopleService = new TestablePeopleService(mContext);
|
||||
mTestableLooper = TestableLooper.get(this);
|
||||
mIPeopleManager = ((IPeopleManager) mPeopleService.mService);
|
||||
mPeopleManager = new PeopleManager(mContext, mIPeopleManager);
|
||||
when(mMockContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
|
||||
when(mCallback.asBinder()).thenReturn(new Binder());
|
||||
|
||||
PeopleService service = new PeopleService(mContext);
|
||||
service.onStart(/* isForTesting= */ true);
|
||||
|
||||
@@ -75,7 +121,7 @@ public final class PeopleServiceTest {
|
||||
mLocalService = (PeopleService.LocalService) mServiceInternal;
|
||||
|
||||
mSessionId = new AppPredictionSessionId("abc", USER_ID);
|
||||
mPredictionContext = new AppPredictionContext.Builder(mContext)
|
||||
mPredictionContext = new AppPredictionContext.Builder(mMockContext)
|
||||
.setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
|
||||
.setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
|
||||
.setExtras(new Bundle())
|
||||
@@ -111,4 +157,134 @@ public final class PeopleServiceTest {
|
||||
|
||||
mServiceInternal.onDestroyPredictionSession(mSessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterConversationListener() throws Exception {
|
||||
assertEquals(0,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
|
||||
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
|
||||
new TestableConversationListener());
|
||||
mTestableLooper.processAllMessages();
|
||||
assertEquals(1,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
|
||||
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
|
||||
new TestableConversationListener());
|
||||
mTestableLooper.processAllMessages();
|
||||
assertEquals(2,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
|
||||
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
|
||||
new TestableConversationListener());
|
||||
mTestableLooper.processAllMessages();
|
||||
assertEquals(3,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisterConversationListener() throws Exception {
|
||||
TestableConversationListener listener1 = new TestableConversationListener();
|
||||
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
|
||||
listener1);
|
||||
TestableConversationListener listener2 = new TestableConversationListener();
|
||||
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
|
||||
listener2);
|
||||
TestableConversationListener listener3 = new TestableConversationListener();
|
||||
mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
|
||||
listener3);
|
||||
mTestableLooper.processAllMessages();
|
||||
assertEquals(3,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
|
||||
mIPeopleManager.unregisterConversationListener(
|
||||
listener2);
|
||||
assertEquals(2,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
mIPeopleManager.unregisterConversationListener(
|
||||
listener1);
|
||||
assertEquals(1,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
mIPeopleManager.unregisterConversationListener(
|
||||
listener3);
|
||||
assertEquals(0,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyTriggersConversationListenersForRegisteredConversation() {
|
||||
PeopleManager.ConversationListener listenerForConversation1 = mock(
|
||||
PeopleManager.ConversationListener.class);
|
||||
registerListener(CONVERSATION_ID_1, listenerForConversation1);
|
||||
PeopleManager.ConversationListener secondListenerForConversation1 = mock(
|
||||
PeopleManager.ConversationListener.class);
|
||||
registerListener(CONVERSATION_ID_1, secondListenerForConversation1);
|
||||
PeopleManager.ConversationListener listenerForConversation2 = mock(
|
||||
PeopleManager.ConversationListener.class);
|
||||
registerListener(CONVERSATION_ID_2, listenerForConversation2);
|
||||
assertEquals(3,
|
||||
mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
|
||||
|
||||
// Update conversation with two listeners.
|
||||
ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1,
|
||||
ACTIVITY_GAME).build();
|
||||
mPeopleService.mConversationListenerHelper.onConversationsUpdate(
|
||||
Arrays.asList(getConversation(CONVERSATION_ID_1, status)));
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Never update listeners for other conversations.
|
||||
verify(listenerForConversation2, never()).onConversationUpdate(any());
|
||||
// Should update both listeners for the conversation.
|
||||
ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass(
|
||||
ConversationChannel.class);
|
||||
verify(listenerForConversation1, times(1)).onConversationUpdate(
|
||||
capturedConversation.capture());
|
||||
ConversationChannel conversationChannel = capturedConversation.getValue();
|
||||
verify(secondListenerForConversation1, times(1)).onConversationUpdate(
|
||||
eq(conversationChannel));
|
||||
assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1);
|
||||
assertThat(conversationChannel.getStatuses()).containsExactly(status);
|
||||
}
|
||||
|
||||
private void registerListener(String conversationId,
|
||||
PeopleManager.ConversationListener listener) {
|
||||
mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(),
|
||||
conversationId, listener,
|
||||
mTestLooper.getNewExecutor());
|
||||
}
|
||||
|
||||
private ConversationChannel getConversation(String shortcutId, ConversationStatus status) {
|
||||
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext,
|
||||
shortcutId).setLongLabel(
|
||||
"name").build();
|
||||
NotificationChannel notificationChannel = new NotificationChannel("123",
|
||||
"channel",
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
return new ConversationChannel(shortcutInfo, 0,
|
||||
notificationChannel, null,
|
||||
123L, false, false, Arrays.asList(status));
|
||||
}
|
||||
|
||||
private class TestableConversationListener extends IConversationListener.Stub {
|
||||
@Override
|
||||
public void onConversationUpdate(ConversationChannel conversation) {
|
||||
}
|
||||
}
|
||||
|
||||
// Use a Testable subclass so we can simulate calls from the system without failing.
|
||||
private static class TestablePeopleService extends PeopleService {
|
||||
TestablePeopleService(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enforceSystemRootOrSystemUI(Context context, String message) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ import android.os.CancellationSignal;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.test.TestLooper;
|
||||
import android.provider.ContactsContract;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
@@ -91,8 +92,11 @@ import com.android.internal.app.ChooserActivity;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.notification.NotificationManagerInternal;
|
||||
import com.android.server.people.PeopleService;
|
||||
import com.android.server.pm.parsing.pkg.AndroidPackage;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -123,6 +127,7 @@ public final class DataManagerTest {
|
||||
private static final String TEST_PKG_NAME = "pkg";
|
||||
private static final String TEST_CLASS_NAME = "class";
|
||||
private static final String TEST_SHORTCUT_ID = "sc";
|
||||
private static final String TEST_SHORTCUT_ID_2 = "sc2";
|
||||
private static final int TEST_PKG_UID = 35;
|
||||
private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
|
||||
private static final String PHONE_NUMBER = "+1234567890";
|
||||
@@ -157,6 +162,7 @@ public final class DataManagerTest {
|
||||
private ShortcutChangeCallback mShortcutChangeCallback;
|
||||
private ShortcutInfo mShortcutInfo;
|
||||
private TestInjector mInjector;
|
||||
private TestLooper mLooper;
|
||||
|
||||
@Before
|
||||
public void setUp() throws PackageManager.NameNotFoundException {
|
||||
@@ -237,7 +243,8 @@ public final class DataManagerTest {
|
||||
mCancellationSignal = new CancellationSignal();
|
||||
|
||||
mInjector = new TestInjector();
|
||||
mDataManager = new DataManager(mContext, mInjector);
|
||||
mLooper = new TestLooper();
|
||||
mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
|
||||
mDataManager.initialize();
|
||||
|
||||
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
|
||||
@@ -514,6 +521,84 @@ public final class DataManagerTest {
|
||||
assertTrue(mDataManager.getRecentConversations(USER_ID_PRIMARY).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddConversationsListener() throws Exception {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME,
|
||||
USER_ID_PRIMARY,
|
||||
TEST_SHORTCUT_ID);
|
||||
|
||||
PeopleService.ConversationsListener listener = mock(
|
||||
PeopleService.ConversationsListener.class);
|
||||
mDataManager.addConversationsListener(listener);
|
||||
|
||||
List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel);
|
||||
verify(listener, times(0)).onConversationsUpdate(eq(changedConversations));
|
||||
mDataManager.notifyConversationsListeners(changedConversations);
|
||||
mLooper.dispatchAll();
|
||||
|
||||
verify(listener, times(1)).onConversationsUpdate(eq(changedConversations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddConversationListenersNotifiesMultipleConversations() throws Exception {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME,
|
||||
USER_ID_PRIMARY,
|
||||
TEST_SHORTCUT_ID);
|
||||
ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY,
|
||||
TEST_SHORTCUT_ID_2,
|
||||
buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut2);
|
||||
ConversationChannel conversationChannel2 = mDataManager.getConversation(TEST_PKG_NAME,
|
||||
USER_ID_PRIMARY,
|
||||
TEST_SHORTCUT_ID_2);
|
||||
PeopleService.ConversationsListener listener = mock(
|
||||
PeopleService.ConversationsListener.class);
|
||||
mDataManager.addConversationsListener(listener);
|
||||
|
||||
List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel,
|
||||
conversationChannel2);
|
||||
verify(listener, times(0)).onConversationsUpdate(eq(changedConversations));
|
||||
mDataManager.notifyConversationsListeners(changedConversations);
|
||||
mLooper.dispatchAll();
|
||||
|
||||
verify(listener, times(1)).onConversationsUpdate(eq(changedConversations));
|
||||
ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass(
|
||||
List.class);
|
||||
verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture());
|
||||
assertThat(capturedConversation.getValue()).containsExactly(conversationChannel,
|
||||
conversationChannel2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddOrUpdateStatusNotifiesConversationsListeners() throws Exception {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
|
||||
buildPerson());
|
||||
mDataManager.addOrUpdateConversationInfo(shortcut);
|
||||
PeopleService.ConversationsListener listener = mock(
|
||||
PeopleService.ConversationsListener.class);
|
||||
mDataManager.addConversationsListener(listener);
|
||||
|
||||
ConversationStatus status = new ConversationStatus.Builder("cs1", ACTIVITY_GAME).build();
|
||||
mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, status);
|
||||
mLooper.dispatchAll();
|
||||
|
||||
ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass(
|
||||
List.class);
|
||||
verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture());
|
||||
ConversationChannel result = Iterables.getOnlyElement(capturedConversation.getValue());
|
||||
assertThat(result.getStatuses()).containsExactly(status);
|
||||
assertEquals(result.getShortcutInfo().getId(), TEST_SHORTCUT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConversationReturnsCustomizedConversation() {
|
||||
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
@@ -975,7 +1060,7 @@ public final class DataManagerTest {
|
||||
mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
|
||||
byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY);
|
||||
|
||||
DataManager dataManager = new DataManager(mContext, mInjector);
|
||||
DataManager dataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
|
||||
dataManager.onUserUnlocked(USER_ID_PRIMARY);
|
||||
dataManager.restore(USER_ID_PRIMARY, payload);
|
||||
ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
|
||||
|
||||
Reference in New Issue
Block a user