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