Update People Space widgets on posted notifications.

Change-Id: I7d715a8ac85db83cffffb85207cd23f20f2952bd
Test: Get notification and see widget updating.
Bug: 169783793
This commit is contained in:
Flavio Fiszman
2020-10-23 15:15:18 +01:00
parent ec820fd42c
commit 40d69a6318
4 changed files with 300 additions and 0 deletions

View File

@@ -65,6 +65,7 @@ import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.app.IBatteryStats;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.LatencyTracker;
import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -188,6 +189,13 @@ public class FrameworkServicesModule {
return context.getSystemService(InputMethodManager.class);
}
@Provides
@Singleton
static IAppWidgetService provideIAppWidgetService() {
return IAppWidgetService.Stub.asInterface(
ServiceManager.getService(Context.APPWIDGET_SERVICE));
}
@Provides
@Singleton
static IPackageManager provideIPackageManager() {

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2020 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.systemui.people.widget;
import android.content.ComponentName;
import android.content.Context;
import android.os.ServiceManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.systemui.R;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import javax.inject.Inject;
import javax.inject.Singleton;
/** Manager for People Space widget. */
@Singleton
public class PeopleSpaceWidgetManager {
private static final String TAG = "PeopleSpaceWidgetMgr";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private final Context mContext;
private IAppWidgetService mAppWidgetManager;
@Inject
public PeopleSpaceWidgetManager(Context context, IAppWidgetService appWidgetService) {
if (DEBUG) Log.d(TAG, "constructor");
mContext = context;
mAppWidgetManager = appWidgetService;
}
/** Constructor used for testing. */
@VisibleForTesting
protected PeopleSpaceWidgetManager(Context context) {
if (DEBUG) Log.d(TAG, "constructor");
mContext = context;
mAppWidgetManager = IAppWidgetService.Stub.asInterface(
ServiceManager.getService(Context.APPWIDGET_SERVICE));
}
/** AppWidgetManager setter used for testing. */
@VisibleForTesting
protected void setAppWidgetManager(IAppWidgetService appWidgetService) {
mAppWidgetManager = appWidgetService;
}
/** Updates People Space widgets. */
public void updateWidgets() {
try {
if (DEBUG) Log.d(TAG, "updateWidgets called");
int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
);
if (widgetIds.length == 0) {
if (DEBUG) Log.d(TAG, "no widgets to update");
return;
}
if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets");
mAppWidgetManager
.notifyAppWidgetViewDataChanged(mContext.getOpPackageName(), widgetIds,
R.id.widget_list_view);
} catch (Exception e) {
Log.e(TAG, "Exception: " + e);
}
}
/**
* Attaches the manager to the pipeline, making it ready to receive events. Should only be
* called once.
*/
public void attach(NotificationListener listenerService) {
if (DEBUG) Log.d(TAG, "attach");
listenerService.addNotificationHandler(mListener);
}
private final NotificationHandler mListener = new NotificationHandler() {
@Override
public void onNotificationPosted(
StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted");
updateWidgets();
}
@Override
public void onNotificationRemoved(
StatusBarNotification sbn,
NotificationListenerService.RankingMap rankingMap
) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved");
updateWidgets();
}
@Override
public void onNotificationRemoved(
StatusBarNotification sbn,
NotificationListenerService.RankingMap rankingMap,
int reason) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved with reason " + reason);
updateWidgets();
}
@Override
public void onNotificationRankingUpdate(
NotificationListenerService.RankingMap rankingMap) { }
@Override
public void onNotificationsInitialized() {
if (DEBUG) Log.d(TAG, "onNotificationsInitialized");
updateWidgets();
}
};
}

View File

@@ -21,6 +21,7 @@ import android.util.Log;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -49,6 +50,7 @@ public class NotifPipelineInitializer implements Dumpable {
private final ShadeListBuilder mListBuilder;
private final NotifCoordinators mNotifPluggableCoordinators;
private final NotifInflaterImpl mNotifInflater;
private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private final DumpManager mDumpManager;
private final ShadeViewManagerFactory mShadeViewManagerFactory;
private final FeatureFlags mFeatureFlags;
@@ -62,6 +64,7 @@ public class NotifPipelineInitializer implements Dumpable {
ShadeListBuilder listBuilder,
NotifCoordinators notifCoordinators,
NotifInflaterImpl notifInflater,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
DumpManager dumpManager,
ShadeViewManagerFactory shadeViewManagerFactory,
FeatureFlags featureFlags) {
@@ -72,6 +75,7 @@ public class NotifPipelineInitializer implements Dumpable {
mNotifPluggableCoordinators = notifCoordinators;
mDumpManager = dumpManager;
mNotifInflater = notifInflater;
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mShadeViewManagerFactory = shadeViewManagerFactory;
mFeatureFlags = featureFlags;
}
@@ -99,6 +103,7 @@ public class NotifPipelineInitializer implements Dumpable {
mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
mGroupCoalescer.attach(notificationService);
mPeopleSpaceWidgetManager.attach(notificationService);
Log.d(TAG, "Notif pipeline initialized");
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2020 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.systemui.people.widget;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
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 static java.util.Objects.requireNonNull;
import android.content.Context;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.notification.collection.NoManSimulator;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
private static final long MIN_LINGER_DURATION = 5;
private static final String TEST_PACKAGE_A = "com.test.package_a";
private static final String TEST_PACKAGE_B = "com.test.package_b";
private PeopleSpaceWidgetManager mManager;
@Mock private NotificationListener mListenerService;
@Mock private IAppWidgetService mIAppWidgetService;
@Mock private Context mContext;
@Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor;
private final NoManSimulator mNoMan = new NoManSimulator();
private final FakeSystemClock mClock = new FakeSystemClock();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mManager =
new PeopleSpaceWidgetManager(mContext);
mManager.setAppWidgetManager(mIAppWidgetService);
mManager.attach(mListenerService);
verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
NotificationHandler serviceListener = requireNonNull(mListenerCaptor.getValue());
mNoMan.addListener(serviceListener);
}
@Test
public void testDoNotNotifyAppWidgetIfNoWidgets() throws RemoteException {
int[] widgetIdsArray = {};
when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
NotifEvent notif1 = mNoMan.postNotif(
new NotificationEntryBuilder()
.setId(0)
.setPkg(TEST_PACKAGE_A));
mClock.advanceTime(MIN_LINGER_DURATION);
verify(mIAppWidgetService, times(1)).getAppWidgetIds(any());
verify(mIAppWidgetService, never()).notifyAppWidgetViewDataChanged(any(), any(), anyInt());
}
@Test
public void testNotifyAppWidgetIfWidgets() throws RemoteException {
int[] widgetIdsArray = {1};
when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
NotifEvent notif1 = mNoMan.postNotif(
new NotificationEntryBuilder()
.setId(0)
.setPkg(TEST_PACKAGE_A));
mClock.advanceTime(MIN_LINGER_DURATION);
verify(mIAppWidgetService, times(1)).getAppWidgetIds(any());
verify(mIAppWidgetService, times(1))
.notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
}
@Test
public void testNotifyAppWidgetTwiceIfTwoNotifications() throws RemoteException {
int[] widgetIdsArray = {1, 2};
when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(1));
mClock.advanceTime(4);
NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_B)
.setId(2));
verify(mIAppWidgetService, times(2)).getAppWidgetIds(any());
verify(mIAppWidgetService, times(2))
.notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
}
@Test
public void testNotifyAppWidgetTwiceIfNotificationPostedAndRemoved() throws RemoteException {
int[] widgetIdsArray = {1, 2};
when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(1));
mClock.advanceTime(4);
NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
verify(mIAppWidgetService, times(2)).getAppWidgetIds(any());
verify(mIAppWidgetService, times(2))
.notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
}
}