1 /* 2 * Copyright (C) 2015 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.settingslib.location; 18 19 import android.app.AppGlobals; 20 import android.app.AppOpsManager; 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.IPackageManager; 24 import android.content.pm.PackageManager; 25 import android.graphics.drawable.Drawable; 26 import android.os.Process; 27 import android.os.RemoteException; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.util.IconDrawableFactory; 31 import android.util.Log; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * Retrieves the information of applications which accessed location recently. 38 */ 39 public class RecentLocationApps { 40 private static final String TAG = RecentLocationApps.class.getSimpleName(); 41 private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; 42 43 private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000; 44 45 private static final int[] LOCATION_OPS = new int[] { 46 AppOpsManager.OP_MONITOR_LOCATION, 47 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 48 }; 49 50 private final PackageManager mPackageManager; 51 private final Context mContext; 52 private final IconDrawableFactory mDrawableFactory; 53 54 public RecentLocationApps(Context context) { 55 mContext = context; 56 mPackageManager = context.getPackageManager(); 57 mDrawableFactory = IconDrawableFactory.newInstance(context); 58 } 59 60 /** 61 * Fills a list of applications which queried location recently within specified time. 62 */ 63 public List<Request> getAppList() { 64 // Retrieve a location usage list from AppOps 65 AppOpsManager aoManager = 66 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 67 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS); 68 69 final int appOpsCount = appOps != null ? appOps.size() : 0; 70 71 // Process the AppOps list and generate a preference list. 72 ArrayList<Request> requests = new ArrayList<>(appOpsCount); 73 final long now = System.currentTimeMillis(); 74 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 75 final List<UserHandle> profiles = um.getUserProfiles(); 76 77 for (int i = 0; i < appOpsCount; ++i) { 78 AppOpsManager.PackageOps ops = appOps.get(i); 79 // Don't show the Android System in the list - it's not actionable for the user. 80 // Also don't show apps belonging to background users except managed users. 81 String packageName = ops.getPackageName(); 82 int uid = ops.getUid(); 83 int userId = UserHandle.getUserId(uid); 84 boolean isAndroidOs = 85 (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName); 86 if (isAndroidOs || !profiles.contains(new UserHandle(userId))) { 87 continue; 88 } 89 Request request = getRequestFromOps(now, ops); 90 if (request != null) { 91 requests.add(request); 92 } 93 } 94 95 return requests; 96 } 97 98 /** 99 * Creates a Request entry for the given PackageOps. 100 * 101 * This method examines the time interval of the PackageOps first. If the PackageOps is older 102 * than the designated interval, this method ignores the PackageOps object and returns null. 103 * When the PackageOps is fresh enough, this method returns a Request object for the package 104 */ 105 private Request getRequestFromOps(long now, 106 AppOpsManager.PackageOps ops) { 107 String packageName = ops.getPackageName(); 108 List<AppOpsManager.OpEntry> entries = ops.getOps(); 109 boolean highBattery = false; 110 boolean normalBattery = false; 111 // Earliest time for a location request to end and still be shown in list. 112 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; 113 for (AppOpsManager.OpEntry entry : entries) { 114 if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) { 115 switch (entry.getOp()) { 116 case AppOpsManager.OP_MONITOR_LOCATION: 117 normalBattery = true; 118 break; 119 case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: 120 highBattery = true; 121 break; 122 default: 123 break; 124 } 125 } 126 } 127 128 if (!highBattery && !normalBattery) { 129 if (Log.isLoggable(TAG, Log.VERBOSE)) { 130 Log.v(TAG, packageName + " hadn't used location within the time interval."); 131 } 132 return null; 133 } 134 135 // The package is fresh enough, continue. 136 137 int uid = ops.getUid(); 138 int userId = UserHandle.getUserId(uid); 139 140 Request request = null; 141 try { 142 IPackageManager ipm = AppGlobals.getPackageManager(); 143 ApplicationInfo appInfo = 144 ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId); 145 if (appInfo == null) { 146 Log.w(TAG, "Null application info retrieved for package " + packageName 147 + ", userId " + userId); 148 return null; 149 } 150 151 final UserHandle userHandle = new UserHandle(userId); 152 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId); 153 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); 154 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); 155 if (appLabel.toString().contentEquals(badgedAppLabel)) { 156 // If badged label is not different from original then no need for it as 157 // a separate content description. 158 badgedAppLabel = null; 159 } 160 request = new Request(packageName, userHandle, icon, appLabel, highBattery, 161 badgedAppLabel); 162 } catch (RemoteException e) { 163 Log.w(TAG, "Error while retrieving application info for package " + packageName 164 + ", userId " + userId, e); 165 } 166 167 return request; 168 } 169 170 public static class Request { 171 public final String packageName; 172 public final UserHandle userHandle; 173 public final Drawable icon; 174 public final CharSequence label; 175 public final boolean isHighBattery; 176 public final CharSequence contentDescription; 177 178 private Request(String packageName, UserHandle userHandle, Drawable icon, 179 CharSequence label, boolean isHighBattery, CharSequence contentDescription) { 180 this.packageName = packageName; 181 this.userHandle = userHandle; 182 this.icon = icon; 183 this.label = label; 184 this.isHighBattery = isHighBattery; 185 this.contentDescription = contentDescription; 186 } 187 } 188 } 189