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.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