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