• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.notification;
17 
18 import android.annotation.Nullable;
19 import android.app.ActivityManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Binder;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.Message;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.service.notification.NotificationListenerService;
30 import android.service.notification.StatusBarNotification;
31 import android.util.Log;
32 
33 import com.android.car.assist.client.CarAssistUtils;
34 
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40 
41 /**
42  * NotificationListenerService that fetches all notifications from system.
43  */
44 public class CarNotificationListener extends NotificationListenerService {
45     private static final String TAG = "CarNotificationListener";
46     static final String ACTION_LOCAL_BINDING = "local_binding";
47     static final int NOTIFY_NOTIFICATION_POSTED = 1;
48     static final int NOTIFY_NOTIFICATION_REMOVED = 2;
49     /** Temporary {@link Ranking} object that serves as a reused value holder */
50     final private Ranking mTemporaryRanking = new Ranking();
51 
52     private Handler mHandler;
53     private RankingMap mRankingMap;
54     private CarHeadsUpNotificationManager mHeadsUpManager;
55     private NotificationDataManager mNotificationDataManager;
56 
57     /**
58      * Map that contains all the active notifications. These notifications may or may not be
59      * visible to the user if they get filtered out. The only time these will be removed from the
60      * map is when the {@llink NotificationListenerService} calls the onNotificationRemoved method.
61      * New notifications will be added to the map from {@link CarHeadsUpNotificationManager}.
62      */
63     private Map<String, StatusBarNotification> mActiveNotifications = new HashMap<>();
64 
65     /**
66      * Call this if to register this service as a system service and connect to HUN. This is useful
67      * if the notification service is being used as a lib instead of a standalone app. The
68      * standalone app version has a manifest entry that will have the same effect.
69      * @param context Context required for registering the service.
70      * @param carUxRestrictionManagerWrapper will have the heads up manager registered with it.
71      * @param carHeadsUpNotificationManager HUN controller.
72      * @param notificationDataManager used for keeping track of additional notification states.
73      */
registerAsSystemService(Context context, CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper, CarHeadsUpNotificationManager carHeadsUpNotificationManager, NotificationDataManager notificationDataManager)74     public void registerAsSystemService(Context context,
75             CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
76             CarHeadsUpNotificationManager carHeadsUpNotificationManager,
77             NotificationDataManager notificationDataManager) {
78         try {
79         mNotificationDataManager = notificationDataManager;
80             registerAsSystemService(context,
81                     new ComponentName(context.getPackageName(), getClass().getCanonicalName()),
82                     ActivityManager.getCurrentUser());
83             mHeadsUpManager = carHeadsUpNotificationManager;
84             carUxRestrictionManagerWrapper.setCarHeadsUpNotificationManager(carHeadsUpNotificationManager);
85         } catch (RemoteException e) {
86             Log.e(TAG, "Unable to register notification listener", e);
87         }
88     }
89 
90     @Override
onCreate()91     public void onCreate() {
92         super.onCreate();
93         mNotificationDataManager = new NotificationDataManager();
94         NotificationApplication app = (NotificationApplication) getApplication();
95         app.getClickHandlerFactory().setNotificationDataManager(mNotificationDataManager);
96 
97         mHeadsUpManager = new CarHeadsUpNotificationManager(/* context= */this,
98                 app.getClickHandlerFactory(),
99                 mNotificationDataManager);
100         app.getCarUxRestrictionWrapper().setCarHeadsUpNotificationManager(mHeadsUpManager);
101     }
102 
103     @Override
onBind(Intent intent)104     public IBinder onBind(Intent intent) {
105         return ACTION_LOCAL_BINDING.equals(intent.getAction())
106                 ? new LocalBinder() : super.onBind(intent);
107     }
108 
109     @Override
onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)110     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
111         Log.d(TAG, "onNotificationPosted: " + sbn);
112         // Notifications should only be shown for the current user and the the notifications from
113         // the system when CarNotification is running as SystemUI component.
114         if (sbn.getUser().getIdentifier() != ActivityManager.getCurrentUser()
115                 && sbn.getUser().getIdentifier() != UserHandle.USER_ALL) {
116             return;
117         }
118         mRankingMap = rankingMap;
119         notifyNotificationPosted(sbn);
120     }
121 
122     @Override
onNotificationRemoved(StatusBarNotification sbn)123     public void onNotificationRemoved(StatusBarNotification sbn) {
124         Log.d(TAG, "onNotificationRemoved: " + sbn);
125         mActiveNotifications.remove(sbn.getKey());
126         mHeadsUpManager.maybeRemoveHeadsUp(sbn);
127         notifyNotificationRemoved(sbn);
128     }
129 
130     @Override
onNotificationRankingUpdate(RankingMap rankingMap)131     public void onNotificationRankingUpdate(RankingMap rankingMap) {
132         mRankingMap = rankingMap;
133         for (StatusBarNotification sbn : mActiveNotifications.values()) {
134             if (!mRankingMap.getRanking(sbn.getKey(), mTemporaryRanking)) {
135                 continue;
136             }
137             String oldOverrideGroupKey = sbn.getOverrideGroupKey();
138             String newOverrideGroupKey = getOverrideGroupKey(sbn.getKey());
139             if (!Objects.equals(oldOverrideGroupKey, newOverrideGroupKey)) {
140                 sbn.setOverrideGroupKey(newOverrideGroupKey);
141             }
142         }
143     }
144 
145     /**
146      * Get the override group key of a {@link StatusBarNotification} given its key.
147      */
148     @Nullable
getOverrideGroupKey(String key)149     private String getOverrideGroupKey(String key) {
150         if (mRankingMap != null) {
151             mRankingMap.getRanking(key, mTemporaryRanking);
152             return mTemporaryRanking.getOverrideGroupKey();
153         }
154         return null;
155     }
156 
157     /**
158      * Get all active notifications.
159      *
160      * @return a map of all active notifications with key being the notification key.
161      */
getNotifications()162     Map<String, StatusBarNotification> getNotifications() {
163         return mActiveNotifications;
164     }
165 
166     @Override
getCurrentRanking()167     public RankingMap getCurrentRanking() {
168         return mRankingMap;
169     }
170 
171     @Override
onListenerConnected()172     public void onListenerConnected() {
173         mActiveNotifications = Stream.of(getActiveNotifications()).collect(
174                 Collectors.toMap(StatusBarNotification::getKey, sbn -> sbn));
175         mRankingMap = super.getCurrentRanking();
176     }
177 
178     @Override
onListenerDisconnected()179     public void onListenerDisconnected() {
180     }
181 
setHandler(Handler handler)182     public void setHandler(Handler handler) {
183         mHandler = handler;
184     }
185 
notifyNotificationRemoved(StatusBarNotification sbn)186     private void notifyNotificationRemoved(StatusBarNotification sbn) {
187         if (mHandler == null) {
188             return;
189         }
190         Message msg = Message.obtain(mHandler);
191         msg.what = NOTIFY_NOTIFICATION_REMOVED;
192         msg.obj = sbn;
193         mHandler.sendMessage(msg);
194     }
195 
notifyNotificationPosted(StatusBarNotification sbn)196     private void notifyNotificationPosted(StatusBarNotification sbn) {
197         mNotificationDataManager.addNewMessageNotification(sbn);
198         mHeadsUpManager.maybeShowHeadsUp(sbn, getCurrentRanking(), mActiveNotifications);
199         if (mHandler == null) {
200             return;
201         }
202         Message msg = Message.obtain(mHandler);
203         msg.what = NOTIFY_NOTIFICATION_POSTED;
204         msg.obj = sbn;
205         mHandler.sendMessage(msg);
206     }
207 
208     class LocalBinder extends Binder {
getService()209         public CarNotificationListener getService() {
210             return CarNotificationListener.this;
211         }
212     }
213 }
214