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