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