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