1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui; 16 17 import android.app.Notification; 18 import android.app.NotificationManager; 19 import android.content.Context; 20 import android.os.Bundle; 21 import android.service.notification.StatusBarNotification; 22 import android.util.ArrayMap; 23 import android.util.ArraySet; 24 import android.util.Log; 25 import android.util.SparseArray; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.internal.messages.nano.SystemMessageProto; 29 30 import java.util.Arrays; 31 32 /** 33 * Foreground service controller, a/k/a Dianne's Dungeon. 34 */ 35 public class ForegroundServiceControllerImpl 36 implements ForegroundServiceController { 37 private static final String TAG = "FgServiceController"; 38 private static final boolean DBG = false; 39 40 private final SparseArray<UserServices> mUserServices = new SparseArray<>(); 41 private final Object mMutex = new Object(); 42 43 public ForegroundServiceControllerImpl(Context context) { 44 } 45 46 @Override 47 public boolean isDungeonNeededForUser(int userId) { 48 synchronized (mMutex) { 49 final UserServices services = mUserServices.get(userId); 50 if (services == null) return false; 51 return services.isDungeonNeeded(); 52 } 53 } 54 55 @Override 56 public void addNotification(StatusBarNotification sbn, int importance) { 57 updateNotification(sbn, importance); 58 } 59 60 @Override 61 public boolean removeNotification(StatusBarNotification sbn) { 62 synchronized (mMutex) { 63 final UserServices userServices = mUserServices.get(sbn.getUserId()); 64 if (userServices == null) { 65 if (DBG) { 66 Log.w(TAG, String.format( 67 "user %d with no known notifications got removeNotification for %s", 68 sbn.getUserId(), sbn)); 69 } 70 return false; 71 } 72 if (isDungeonNotification(sbn)) { 73 // if you remove the dungeon entirely, we take that to mean there are 74 // no running services 75 userServices.setRunningServices(null); 76 return true; 77 } else { 78 // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE 79 return userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); 80 } 81 } 82 } 83 84 @Override 85 public void updateNotification(StatusBarNotification sbn, int newImportance) { 86 synchronized (mMutex) { 87 UserServices userServices = mUserServices.get(sbn.getUserId()); 88 if (userServices == null) { 89 userServices = new UserServices(); 90 mUserServices.put(sbn.getUserId(), userServices); 91 } 92 93 if (isDungeonNotification(sbn)) { 94 final Bundle extras = sbn.getNotification().extras; 95 if (extras != null) { 96 final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS); 97 userServices.setRunningServices(svcs); // null ok 98 } 99 } else { 100 userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); 101 if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) 102 && newImportance > NotificationManager.IMPORTANCE_MIN) { 103 userServices.addNotification(sbn.getPackageName(), sbn.getKey()); 104 } 105 } 106 } 107 } 108 109 @Override 110 public boolean isDungeonNotification(StatusBarNotification sbn) { 111 return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES 112 && sbn.getTag() == null 113 && sbn.getPackageName().equals("android"); 114 } 115 116 /** 117 * Struct to track relevant packages and notifications for a userid's foreground services. 118 */ 119 private static class UserServices { 120 private String[] mRunning = null; 121 private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1); 122 public void setRunningServices(String[] pkgs) { 123 mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null; 124 } 125 public void addNotification(String pkg, String key) { 126 if (mNotifications.get(pkg) == null) { 127 mNotifications.put(pkg, new ArraySet<String>()); 128 } 129 mNotifications.get(pkg).add(key); 130 } 131 public boolean removeNotification(String pkg, String key) { 132 final boolean found; 133 final ArraySet<String> keys = mNotifications.get(pkg); 134 if (keys == null) { 135 found = false; 136 } else { 137 found = keys.remove(key); 138 if (keys.size() == 0) { 139 mNotifications.remove(pkg); 140 } 141 } 142 return found; 143 } 144 public boolean isDungeonNeeded() { 145 if (mRunning != null) { 146 for (String pkg : mRunning) { 147 final ArraySet<String> set = mNotifications.get(pkg); 148 if (set == null || set.size() == 0) { 149 return true; 150 } 151 } 152 } 153 return false; 154 } 155 } 156 } 157