1 /* 2 * Copyright (C) 2022 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 17 package com.android.server.pm; 18 19 import android.app.ActivityManager; 20 import android.app.ActivityManager.RunningAppProcessInfo; 21 import android.app.ActivityManagerInternal; 22 import android.app.ActivityThread; 23 import android.app.usage.NetworkStats; 24 import android.app.usage.NetworkStatsManager; 25 import android.content.Context; 26 import android.content.pm.PackageManagerInternal; 27 import android.media.AudioManager; 28 import android.media.IAudioService; 29 import android.net.ConnectivityManager; 30 import android.os.ServiceManager; 31 import android.os.SystemProperties; 32 import android.telecom.TelecomManager; 33 import android.text.TextUtils; 34 import android.util.ArraySet; 35 36 import com.android.internal.util.ArrayUtils; 37 import com.android.server.LocalServices; 38 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.concurrent.TimeUnit; 43 44 /** 45 * A helper class to provide queries for app states concerning gentle-update. 46 */ 47 public class AppStateHelper { 48 // The duration to monitor network usage to determine if network is active or not 49 private static final long ACTIVE_NETWORK_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(10); 50 51 private final Context mContext; 52 AppStateHelper(Context context)53 public AppStateHelper(Context context) { 54 mContext = context; 55 } 56 57 /** 58 * True if the package is loaded into the process. 59 */ isPackageLoaded(RunningAppProcessInfo info, String packageName)60 private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) { 61 return ArrayUtils.contains(info.pkgList, packageName) 62 || ArrayUtils.contains(info.pkgDeps, packageName); 63 } 64 65 /** 66 * Returns the importance of the given package. 67 */ getImportance(String packageName)68 private int getImportance(String packageName) { 69 var am = mContext.getSystemService(ActivityManager.class); 70 return am.getPackageImportance(packageName); 71 } 72 73 /** 74 * True if the app owns the audio focus. 75 */ hasAudioFocus(String packageName)76 private boolean hasAudioFocus(String packageName) { 77 var audioService = IAudioService.Stub.asInterface( 78 ServiceManager.getService(Context.AUDIO_SERVICE)); 79 try { 80 var focusInfos = audioService.getFocusStack(); 81 int size = focusInfos.size(); 82 var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null; 83 return TextUtils.equals(packageName, audioFocusPackage); 84 } catch (Exception ignore) { 85 } 86 return false; 87 } 88 89 /** 90 * True if any app is using voice communication. 91 */ hasVoiceCall()92 private boolean hasVoiceCall() { 93 var am = mContext.getSystemService(AudioManager.class); 94 try { 95 int audioMode = am.getMode(); 96 return audioMode == AudioManager.MODE_IN_CALL 97 || audioMode == AudioManager.MODE_IN_COMMUNICATION; 98 } catch (Exception ignore) { 99 return false; 100 } 101 } 102 103 /** 104 * True if the app is recording audio. 105 */ isRecordingAudio(String packageName)106 private boolean isRecordingAudio(String packageName) { 107 var am = mContext.getSystemService(AudioManager.class); 108 try { 109 for (var arc : am.getActiveRecordingConfigurations()) { 110 if (TextUtils.equals(arc.getClientPackageName(), packageName)) { 111 return true; 112 } 113 } 114 } catch (Exception ignore) { 115 } 116 return false; 117 } 118 119 /** 120 * True if the app is in the foreground. 121 */ isAppForeground(String packageName)122 private boolean isAppForeground(String packageName) { 123 return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 124 } 125 126 /** 127 * True if the app is currently at the top of the screen that the user is interacting with. 128 */ isAppTopVisible(String packageName)129 public boolean isAppTopVisible(String packageName) { 130 return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND; 131 } 132 133 /** 134 * True if the app is playing/recording audio. 135 */ hasActiveAudio(String packageName)136 private boolean hasActiveAudio(String packageName) { 137 return hasAudioFocus(packageName) || isRecordingAudio(packageName); 138 } 139 hasActiveNetwork(List<String> packageNames, int networkType)140 private boolean hasActiveNetwork(List<String> packageNames, int networkType) { 141 var pm = ActivityThread.getPackageManager(); 142 var nsm = mContext.getSystemService(NetworkStatsManager.class); 143 var endTime = System.currentTimeMillis(); 144 var startTime = endTime - ACTIVE_NETWORK_DURATION_MILLIS; 145 try (var stats = nsm.querySummary(networkType, null, startTime, endTime)) { 146 var bucket = new NetworkStats.Bucket(); 147 while (stats.hasNextBucket()) { 148 stats.getNextBucket(bucket); 149 var packageName = pm.getNameForUid(bucket.getUid()); 150 if (!packageNames.contains(packageName)) { 151 continue; 152 } 153 if (bucket.getRxPackets() > 0 || bucket.getTxPackets() > 0) { 154 return true; 155 } 156 } 157 } catch (Exception ignore) { 158 } 159 return false; 160 } 161 162 /** 163 * True if {@code arr} contains any element in {@code which}. 164 * Both {@code arr} and {@code which} must be sorted in advance. 165 */ containsAny(String[] arr, List<String> which)166 private static boolean containsAny(String[] arr, List<String> which) { 167 int s1 = arr.length; 168 int s2 = which.size(); 169 for (int i = 0, j = 0; i < s1 && j < s2; ) { 170 int val = arr[i].compareTo(which.get(j)); 171 if (val == 0) { 172 return true; 173 } else if (val < 0) { 174 ++i; 175 } else { 176 ++j; 177 } 178 } 179 return false; 180 } 181 addLibraryDependency(ArraySet<String> results, List<String> libPackageNames)182 private void addLibraryDependency(ArraySet<String> results, List<String> libPackageNames) { 183 var pmInternal = LocalServices.getService(PackageManagerInternal.class); 184 185 var libraryNames = new ArrayList<String>(); 186 var staticSharedLibraryNames = new ArrayList<String>(); 187 var sdkLibraryNames = new ArrayList<String>(); 188 for (var packageName : libPackageNames) { 189 var pkg = pmInternal.getAndroidPackage(packageName); 190 if (pkg == null) { 191 continue; 192 } 193 libraryNames.addAll(pkg.getLibraryNames()); 194 var libraryName = pkg.getStaticSharedLibraryName(); 195 if (libraryName != null) { 196 staticSharedLibraryNames.add(libraryName); 197 } 198 libraryName = pkg.getSdkLibraryName(); 199 if (libraryName != null) { 200 sdkLibraryNames.add(libraryName); 201 } 202 } 203 204 if (libraryNames.isEmpty() 205 && staticSharedLibraryNames.isEmpty() 206 && sdkLibraryNames.isEmpty()) { 207 return; 208 } 209 210 Collections.sort(libraryNames); 211 Collections.sort(sdkLibraryNames); 212 Collections.sort(staticSharedLibraryNames); 213 214 pmInternal.forEachPackageState(pkgState -> { 215 var pkg = pkgState.getPkg(); 216 if (pkg == null) { 217 return; 218 } 219 if (containsAny(pkg.getUsesLibrariesSorted(), libraryNames) 220 || containsAny(pkg.getUsesOptionalLibrariesSorted(), libraryNames) 221 || containsAny(pkg.getUsesStaticLibrariesSorted(), staticSharedLibraryNames) 222 || containsAny(pkg.getUsesSdkLibrariesSorted(), sdkLibraryNames)) { 223 results.add(pkg.getPackageName()); 224 } 225 }); 226 } 227 228 /** 229 * True if any app has sent or received network data over the past 230 * {@link #ACTIVE_NETWORK_DURATION_MILLIS} milliseconds. 231 */ hasActiveNetwork(List<String> packageNames)232 private boolean hasActiveNetwork(List<String> packageNames) { 233 return hasActiveNetwork(packageNames, ConnectivityManager.TYPE_WIFI) 234 || hasActiveNetwork(packageNames, ConnectivityManager.TYPE_MOBILE); 235 } 236 237 /** 238 * True if any app is interacting with the user. 239 */ hasInteractingApp(List<String> packageNames)240 public boolean hasInteractingApp(List<String> packageNames) { 241 for (var packageName : packageNames) { 242 if (hasActiveAudio(packageName) 243 || isAppTopVisible(packageName)) { 244 return true; 245 } 246 } 247 return hasActiveNetwork(packageNames); 248 } 249 250 /** 251 * True if any app is in the foreground. 252 */ hasForegroundApp(List<String> packageNames)253 public boolean hasForegroundApp(List<String> packageNames) { 254 for (var packageName : packageNames) { 255 if (isAppForeground(packageName)) { 256 return true; 257 } 258 } 259 return false; 260 } 261 262 /** 263 * True if any app is top visible. 264 */ hasTopVisibleApp(List<String> packageNames)265 public boolean hasTopVisibleApp(List<String> packageNames) { 266 for (var packageName : packageNames) { 267 if (isAppTopVisible(packageName)) { 268 return true; 269 } 270 } 271 return false; 272 } 273 274 /** 275 * True if there is an ongoing phone call. 276 */ isInCall()277 public boolean isInCall() { 278 // Simulate in-call during test 279 if (SystemProperties.getBoolean( 280 "debug.pm.gentle_update_test.is_in_call", false)) { 281 return true; 282 } 283 // TelecomManager doesn't handle the case where some apps don't implement ConnectionService. 284 // We check apps using voice communication to detect if the device is in call. 285 var tm = mContext.getSystemService(TelecomManager.class); 286 return tm.isInCall() || hasVoiceCall(); 287 } 288 289 /** 290 * Returns a list of packages which depend on {@code packageNames}. These are the packages 291 * that will be affected when updating {@code packageNames} and should participate in 292 * the evaluation of install constraints. 293 */ getDependencyPackages(List<String> packageNames)294 public List<String> getDependencyPackages(List<String> packageNames) { 295 var results = new ArraySet<String>(); 296 // Include packages sharing the same process 297 var am = mContext.getSystemService(ActivityManager.class); 298 for (var info : am.getRunningAppProcesses()) { 299 for (var packageName : packageNames) { 300 if (!isPackageLoaded(info, packageName)) { 301 continue; 302 } 303 for (var pkg : info.pkgList) { 304 results.add(pkg); 305 } 306 } 307 } 308 // Include packages using bounded services 309 var amInternal = LocalServices.getService(ActivityManagerInternal.class); 310 for (var packageName : packageNames) { 311 results.addAll(amInternal.getClientPackages(packageName)); 312 } 313 // Include packages using libraries 314 addLibraryDependency(results, packageNames); 315 316 return new ArrayList<>(results); 317 } 318 } 319