1 /*
2  * Copyright (C) 2020 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.google.android.car.userswitchmonitor;
17 
18 import android.annotation.UserIdInt;
19 import android.app.Notification;
20 import android.app.NotificationChannel;
21 import android.app.NotificationManager;
22 import android.app.Service;
23 import android.car.Car;
24 import android.car.user.CarUserManager;
25 import android.car.user.CarUserManager.UserLifecycleEvent;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.os.Bundle;
31 import android.os.IBinder;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.text.format.DateFormat;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.GuardedBy;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.util.ArrayList;
42 import java.util.LinkedHashMap;
43 import java.util.List;
44 
45 /**
46  * Service that users {@link CarUserManager.UserLifecycleEvent UserLifecycleEvents} to monitor
47  * user switches.
48  *
49  */
50 public final class UserSwitchMonitorService extends Service {
51 
52     static final String TAG = "UserSwitchMonitor";
53 
54     private static final String CMD_CLEAR = "clear";
55     private static final String CMD_HELP = "help";
56     private static final String CMD_REGISTER = "register";
57     private static final String CMD_UNREGISTER = "unregister";
58 
59     private final Object mLock = new Object();
60     private final int mUserId = android.os.Process.myUserHandle().getIdentifier();
61     private final MyListener mListener = new MyListener();
62 
63     @GuardedBy("mLock")
64     private final List<Event> mEvents = new ArrayList<>();
65 
66     @GuardedBy("mLock")
67     private final LinkedHashMap<Integer, MyReceiver> mReceivers = new LinkedHashMap<>();
68 
69     private Car mCar;
70     private CarUserManager mCarUserManager;
71     private NotificationManager mNotificationManager;
72 
73     @Override
onCreate()74     public void onCreate() {
75         mCar = Car.createCar(this, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
76                 (car, ready) -> onCarReady(car, ready));
77     }
78 
onCarReady(Car car, boolean ready)79     private void onCarReady(Car car, boolean ready) {
80         Log.d(TAG, "onCarReady(): ready=" + ready);
81         if (!ready) {
82             Log.w(TAG, "Car not ready yet");
83             return;
84         }
85         mCarUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
86         registerListener();
87         mNotificationManager = getSystemService(NotificationManager.class);
88 
89         UserManager um = getSystemService(UserManager.class);
90         List<UserHandle> users = um.getUserHandles(/* excludeDying= */ false);
91         Log.d(TAG, "Users on create: " + users);
92         users.forEach((u) -> registerReceiver(u.getIdentifier()));
93     }
94 
registerListener()95     private void registerListener() {
96         Log.d(TAG, "registerListener(): " + mListener);
97         try {
98             mCarUserManager.addListener((r)-> r.run(), mListener);
99         } catch (Exception e) {
100             // Most likely the permission was not granted
101             Log.w(TAG, "Could not add listener for user " + getUser() + ": " + e);
102         }
103     }
104 
registerReceiver(@serIdInt int userId)105     private void registerReceiver(@UserIdInt int userId) {
106         Log.d(TAG, "registerReceiver(): userId: " + userId);
107         MyReceiver receiver;
108         Context context;
109         synchronized (mLock) {
110             if (mReceivers.containsKey(userId)) {
111                 Log.d(TAG, "registerReceiver(): already registered for userId: " + userId);
112                 return;
113             }
114             context = getContextForUser(userId);
115             if (context == null) {
116                 return;
117             }
118             receiver = new MyReceiver(userId, context);
119             Log.d(TAG, "Saving receiver for user " + userId + ": " + receiver);
120             mReceivers.put(userId, receiver);
121         }
122 
123         IntentFilter filter = new IntentFilter();
124         filter.addAction(Intent.ACTION_BOOT_COMPLETED);
125         filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
126         filter.addAction(Intent.ACTION_PRE_BOOT_COMPLETED);
127         filter.addAction(Intent.ACTION_USER_ADDED);
128         filter.addAction(Intent.ACTION_USER_BACKGROUND);
129         filter.addAction(Intent.ACTION_USER_FOREGROUND);
130         filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
131         filter.addAction(Intent.ACTION_USER_INITIALIZE);
132         filter.addAction(Intent.ACTION_USER_PRESENT);
133         filter.addAction(Intent.ACTION_USER_REMOVED);
134         filter.addAction(Intent.ACTION_USER_STARTED);
135         filter.addAction(Intent.ACTION_USER_STARTING);
136         filter.addAction(Intent.ACTION_USER_STOPPED);
137         filter.addAction(Intent.ACTION_USER_SWITCHED);
138 
139         context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
140     }
141 
getContextForUser(@serIdInt int userId)142     private Context getContextForUser(@UserIdInt int userId) {
143         try {
144             return createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
145         } catch (Exception e) {
146             Log.w(TAG, "getContextForUser(): could not get context for " + userId
147                     + " - did you install the app on it? Exception: " + e);
148             return null;
149         }
150     }
151 
152     @Override
onStartCommand(Intent intent, int flags, int startId)153     public int onStartCommand(Intent intent, int flags, int startId) {
154         Log.d(TAG, "onStartCommand(" + mUserId + "): " + intent);
155 
156         String channelId = "4815162342";
157         String name = "UserSwitchMonitor";
158         NotificationChannel channel = new NotificationChannel(channelId, name,
159                 NotificationManager.IMPORTANCE_MIN);
160         mNotificationManager.createNotificationChannel(channel);
161 
162         // Cannot use R.drawable because package name is different on app2
163         int iconResId = getApplicationInfo().icon;
164         startForeground(startId,
165                 new Notification.Builder(this, channelId)
166                         .setContentText(name)
167                         .setContentTitle(name)
168                         .setSmallIcon(iconResId)
169                         .build());
170 
171         return super.onStartCommand(intent, flags, startId);
172     }
173 
174     @Override
onDestroy()175     public void onDestroy() {
176         Log.d(TAG, "onDestroy(" + mUserId + ")");
177 
178         unregisterListener();
179 
180         synchronized (mLock) {
181             mReceivers.values().forEach(MyReceiver::unregister);
182         }
183 
184         if (mCar != null && mCar.isConnected()) {
185             mCar.disconnect();
186         }
187         super.onDestroy();
188     }
189 
unregisterListener()190     private void unregisterListener() {
191         Log.d(TAG, "unregisterListener(): " + mListener);
192         if (mCarUserManager == null) {
193             Log.w(TAG, "Cannot remove listener because manager is null");
194             return;
195         }
196         try {
197             mCarUserManager.removeListener(mListener);
198         } catch (Exception e) {
199             // Most likely the permission was not granted
200             Log.w(TAG, "Could not remove listener for user " + getUser() + ": " + e);
201         }
202     }
203 
204     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)205     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
206         if (args != null && args.length > 0) {
207             executeCommand(pw, args);
208             return;
209         }
210         pw.printf("User id: %d\n", mUserId);
211         pw.printf("Listener: %s\n", mListener);
212 
213         String indent = "  ";
214         synchronized (mLock) {
215             pw.printf("Receivers for %d users:\n", mReceivers.size());
216             mReceivers.values().forEach((receiver) -> pw.printf("%s%s\n", indent, receiver));
217 
218             if (mEvents.isEmpty()) {
219                 pw.println("Did not receive any event yet");
220                 return;
221             }
222             int eventsSize = mEvents.size();
223             pw.printf("Received %d events:\n", eventsSize);
224             for (int i = 0; i < eventsSize; i++) {
225                 pw.printf("%s%d: %s\n", indent, (i + 1), mEvents.get(i));
226             }
227         }
228     }
229 
230     @Override
onBind(Intent intent)231     public IBinder onBind(Intent intent) {
232         Log.d(TAG, "onBind(): " + intent);
233         return null;
234     }
235 
executeCommand(PrintWriter pw, String[] args)236     private void executeCommand(PrintWriter pw, String[] args) {
237         String cmd = args[0];
238         switch (cmd) {
239             case CMD_CLEAR:
240                 cmdClear(pw);
241                 break;
242             case CMD_HELP:
243                 cmdHelp(pw);
244                 break;
245             case CMD_REGISTER:
246                 cmdRegister(pw);
247                 break;
248             case CMD_UNREGISTER:
249                 cmdUnregister(pw);
250                 break;
251             default:
252                 pw.printf("invalid command: %s\n\n",  cmd);
253                 cmdHelp(pw);
254         }
255     }
256 
cmdHelp(PrintWriter pw)257     private void cmdHelp(PrintWriter pw) {
258         pw.printf("Options:\n");
259         pw.printf("  help: show this help\n");
260         pw.printf("  clear: clear the list of received events\n");
261         pw.printf("  register: register the service to receive events\n");
262         pw.printf("  unregister: unregister the service from receiving events\n");
263     }
264 
cmdRegister(PrintWriter pw)265     private void cmdRegister(PrintWriter pw) {
266         pw.printf("registering listener %s\n", mListener);
267         runCmd(pw, () -> registerListener());
268     }
269 
cmdUnregister(PrintWriter pw)270     private void cmdUnregister(PrintWriter pw) {
271         pw.printf("unregistering listener %s\n", mListener);
272         runCmd(pw, () -> unregisterListener());
273     }
274 
cmdClear(PrintWriter pw)275     private void cmdClear(PrintWriter pw) {
276         int size;
277         synchronized (mLock) {
278             size = mEvents.size();
279             mEvents.clear();
280         }
281         String msg = String.format("Cleared %d events", size);
282         Log.i(TAG, msg);
283         pw.println(msg);
284     }
285 
runCmd(PrintWriter pw, Runnable r)286     private void runCmd(PrintWriter pw, Runnable r) {
287         try {
288             r.run();
289         } catch (Exception e) {
290             Log.e(TAG, "error running command", e);
291             pw.printf("failed: %s\n", e);
292         }
293     }
294 
toString(@serIdInt int userId, Intent intent)295     private static String toString(@UserIdInt int userId, Intent intent) {
296         StringBuilder string = new StringBuilder("Intent[onUser=").append(userId)
297                 .append(",action=").append(intent.getAction());
298         Bundle extras = intent.getExtras();
299         if (extras != null) {
300             int numberExtras = extras.size();
301             string.append(", ").append(numberExtras).append(" extra");
302             if (numberExtras > 1) {
303                 string.append('s');
304             }
305             string.append(": {");
306             int i = 0;
307             for (String key : extras.keySet()) {
308                 @SuppressWarnings("deprecation")
309                 Object value = extras.get(key);
310                 string.append(key).append('=').append(value);
311                 if (++i < numberExtras) {
312                     string.append(", ");
313                 }
314             }
315             string.append('}');
316         }
317         return string.append(']').toString();
318     }
319 
320     private final class MyListener implements CarUserManager.UserLifecycleListener {
321 
322         private int mNumberCalls;
323 
324         @Override
onEvent(UserLifecycleEvent event)325         public void onEvent(UserLifecycleEvent event) {
326             Log.d(TAG, "onEvent(" + mUserId + "): event=" + event + ", numberCalls="
327                     + (++mNumberCalls));
328             synchronized (mLock) {
329                 mEvents.add(new Event(event));
330             }
331             // NOTE: if USER_LIFECYCLE_EVENT_TYPE_CREATED / USER_LIFECYCLE_EVENT_TYPE_REMOVED are
332             // sent to apps, we could dynamically register / unregister new receivers here
333         }
334 
335         @Override
toString()336         public String toString() {
337             return "MyListener[numberCalls=" + mNumberCalls + "]";
338         }
339     }
340 
341     private final class MyReceiver extends BroadcastReceiver {
342 
343         private final @UserIdInt int mUserId;
344         private final Context mContext;
345 
346         private int mNumberCalls;
347 
MyReceiver(@serIdInt int userId, Context context)348         MyReceiver(@UserIdInt int userId, Context context) {
349             mUserId = userId;
350             mContext = context;
351         }
352 
353         @Override
onReceive(Context context, Intent intent)354         public void onReceive(Context context, Intent intent) {
355             String userFriendlyIntent = UserSwitchMonitorService.toString(mUserId, intent);
356             Log.d(TAG, "onReceive(): intent=" + userFriendlyIntent
357                     + ",context.userId=" + context.getUserId()
358                     + ", numberCalls=" + (++mNumberCalls));
359             synchronized (mLock) {
360                 mEvents.add(new Event(userFriendlyIntent));
361             }
362         }
363 
364         @Override
toString()365         public String toString() {
366             return "MyReceiver[userId=" + mUserId + ", numberCalls=" + mNumberCalls + "]";
367         }
368 
unregister()369         public void unregister() {
370             Log.d(TAG, "Unregistering " + this);
371             mContext.unregisterReceiver(this);
372         }
373     }
374 
375     private static final class Event {
376         private final long mTimestamp = System.currentTimeMillis();
377         private final Object mEvent;
378 
Event(Object event)379         private Event(Object event) {
380             mEvent = event;
381         }
382 
383         @Override
toString()384         public String toString() {
385             return "on " + DateFormat.format("MM-dd HH:mm:ss", mTimestamp) + "(" + mTimestamp
386                     + "): " + mEvent;
387         }
388     }
389 }
390