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 RecentLocationApps(Context context)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 */ getAppList()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 getAppListSorted()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 */ getRequestFromOps(long now, AppOpsManager.PackageOps ops)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 Request(String packageName, UserHandle userHandle, Drawable icon, CharSequence label, boolean isHighBattery, CharSequence contentDescription, long requestFinishTime)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