Home | History | Annotate | Download | only in location
      1 /*
      2  * Copyright (C) 2018 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.time.Clock;
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.Comparator;
     38 import java.util.List;
     39 
     40 /**
     41  * Retrieves the information of applications which accessed location recently.
     42  */
     43 public class RecentLocationAccesses {
     44     private static final String TAG = RecentLocationAccesses.class.getSimpleName();
     45     @VisibleForTesting
     46     static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
     47 
     48     // Keep last 24 hours of location app information.
     49     private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
     50 
     51     /** The flags for querying ops that are trusted for showing in the UI. */
     52     public static final int TRUSTED_STATE_FLAGS = AppOpsManager.OP_FLAG_SELF
     53             | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY
     54             | AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
     55 
     56     @VisibleForTesting
     57     static final int[] LOCATION_OPS = new int[]{
     58             AppOpsManager.OP_FINE_LOCATION,
     59             AppOpsManager.OP_COARSE_LOCATION,
     60     };
     61 
     62     private final PackageManager mPackageManager;
     63     private final Context mContext;
     64     private final IconDrawableFactory mDrawableFactory;
     65     private final Clock mClock;
     66 
     67     public RecentLocationAccesses(Context context) {
     68         this(context, Clock.systemDefaultZone());
     69     }
     70 
     71     @VisibleForTesting
     72     RecentLocationAccesses(Context context, Clock clock) {
     73         mContext = context;
     74         mPackageManager = context.getPackageManager();
     75         mDrawableFactory = IconDrawableFactory.newInstance(context);
     76         mClock = clock;
     77     }
     78 
     79     /**
     80      * Fills a list of applications which queried location recently within specified time.
     81      * Apps are sorted by recency. Apps with more recent location accesses are in the front.
     82      */
     83     public List<Access> getAppList() {
     84         // Retrieve a location usage list from AppOps
     85         PackageManager pm = mContext.getPackageManager();
     86         AppOpsManager aoManager =
     87                 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
     88         List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
     89 
     90         final int appOpsCount = appOps != null ? appOps.size() : 0;
     91 
     92         // Process the AppOps list and generate a preference list.
     93         ArrayList<Access> accesses = new ArrayList<>(appOpsCount);
     94         final long now = mClock.millis();
     95         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
     96         final List<UserHandle> profiles = um.getUserProfiles();
     97 
     98         for (int i = 0; i < appOpsCount; ++i) {
     99             AppOpsManager.PackageOps ops = appOps.get(i);
    100             String packageName = ops.getPackageName();
    101             int uid = ops.getUid();
    102             UserHandle user = UserHandle.getUserHandleForUid(uid);
    103 
    104             // Don't show apps belonging to background users except managed users.
    105             if (!profiles.contains(user)) {
    106                 continue;
    107             }
    108 
    109             // Don't show apps that do not have user sensitive location permissions
    110             boolean showApp = true;
    111             for (int op : LOCATION_OPS) {
    112                 final String permission = AppOpsManager.opToPermission(op);
    113                 final int permissionFlags = pm.getPermissionFlags(permission, packageName, user);
    114                 if (PermissionChecker.checkPermission(mContext, permission, -1, uid, packageName)
    115                         == PermissionChecker.PERMISSION_GRANTED) {
    116                     if ((permissionFlags
    117                             & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) {
    118                         showApp = false;
    119                         break;
    120                     }
    121                 } else {
    122                     if ((permissionFlags
    123                             & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) {
    124                         showApp = false;
    125                         break;
    126                     }
    127                 }
    128             }
    129             if (showApp) {
    130                 Access access = getAccessFromOps(now, ops);
    131                 if (access != null) {
    132                     accesses.add(access);
    133                 }
    134             }
    135         }
    136         return accesses;
    137     }
    138 
    139     public List<Access> getAppListSorted() {
    140         List<Access> accesses = getAppList();
    141         // Sort the list of Access by recency. Most recent accesses first.
    142         Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() {
    143             @Override
    144             public int compare(Access access1, Access access2) {
    145                 return Long.compare(access1.accessFinishTime, access2.accessFinishTime);
    146             }
    147         }));
    148         return accesses;
    149     }
    150 
    151     /**
    152      * Creates a Access entry for the given PackageOps.
    153      *
    154      * This method examines the time interval of the PackageOps first. If the PackageOps is older
    155      * than the designated interval, this method ignores the PackageOps object and returns null.
    156      * When the PackageOps is fresh enough, this method returns a Access object for the package
    157      */
    158     private Access getAccessFromOps(long now,
    159             AppOpsManager.PackageOps ops) {
    160         String packageName = ops.getPackageName();
    161         List<AppOpsManager.OpEntry> entries = ops.getOps();
    162         long locationAccessFinishTime = 0L;
    163         // Earliest time for a location access to end and still be shown in list.
    164         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
    165         for (AppOpsManager.OpEntry entry : entries) {
    166             locationAccessFinishTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
    167         }
    168         // Bail out if the entry is out of date.
    169         if (locationAccessFinishTime < recentLocationCutoffTime) {
    170             return null;
    171         }
    172 
    173         // The package is fresh enough, continue.
    174         int uid = ops.getUid();
    175         int userId = UserHandle.getUserId(uid);
    176 
    177         Access access = null;
    178         try {
    179             ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
    180                     packageName, PackageManager.GET_META_DATA, userId);
    181             if (appInfo == null) {
    182                 Log.w(TAG, "Null application info retrieved for package " + packageName
    183                         + ", userId " + userId);
    184                 return null;
    185             }
    186 
    187             final UserHandle userHandle = new UserHandle(userId);
    188             Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId);
    189             CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
    190             CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
    191             if (appLabel.toString().contentEquals(badgedAppLabel)) {
    192                 // If badged label is not different from original then no need for it as
    193                 // a separate content description.
    194                 badgedAppLabel = null;
    195             }
    196             access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel,
    197                     locationAccessFinishTime);
    198         } catch (NameNotFoundException e) {
    199             Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
    200         }
    201         return access;
    202     }
    203 
    204     public static class Access {
    205         public final String packageName;
    206         public final UserHandle userHandle;
    207         public final Drawable icon;
    208         public final CharSequence label;
    209         public final CharSequence contentDescription;
    210         public final long accessFinishTime;
    211 
    212         public Access(String packageName, UserHandle userHandle, Drawable icon,
    213                 CharSequence label, CharSequence contentDescription,
    214                 long accessFinishTime) {
    215             this.packageName = packageName;
    216             this.userHandle = userHandle;
    217             this.icon = icon;
    218             this.label = label;
    219             this.contentDescription = contentDescription;
    220             this.accessFinishTime = accessFinishTime;
    221         }
    222     }
    223 }
    224