Home | History | Annotate | Download | only in location
      1 /*
      2  * Copyright (C) 2013 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.settings.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.PackageManager;
     24 import android.content.pm.IPackageManager;
     25 import android.content.res.Resources;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.Bundle;
     28 import android.os.Process;
     29 import android.os.RemoteException;
     30 import android.os.UserHandle;
     31 import android.os.UserManager;
     32 import android.preference.Preference;
     33 import android.util.Log;
     34 
     35 import com.android.settings.DimmableIconPreference;
     36 import com.android.settings.R;
     37 import com.android.settings.SettingsActivity;
     38 import com.android.settings.applications.InstalledAppDetails;
     39 
     40 import java.util.ArrayList;
     41 import java.util.List;
     42 
     43 /**
     44  * Retrieves the information of applications which accessed location recently.
     45  */
     46 public class RecentLocationApps {
     47     private static final String TAG = RecentLocationApps.class.getSimpleName();
     48     private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
     49 
     50     private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
     51 
     52     private final SettingsActivity mActivity;
     53     private final PackageManager mPackageManager;
     54 
     55     public RecentLocationApps(SettingsActivity activity) {
     56         mActivity = activity;
     57         mPackageManager = activity.getPackageManager();
     58     }
     59 
     60     private class PackageEntryClickedListener
     61             implements Preference.OnPreferenceClickListener {
     62         private String mPackage;
     63         private UserHandle mUserHandle;
     64 
     65         public PackageEntryClickedListener(String packageName, UserHandle userHandle) {
     66             mPackage = packageName;
     67             mUserHandle = userHandle;
     68         }
     69 
     70         @Override
     71         public boolean onPreferenceClick(Preference preference) {
     72             // start new fragment to display extended information
     73             Bundle args = new Bundle();
     74             args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage);
     75             mActivity.startPreferencePanelAsUser(InstalledAppDetails.class.getName(), args,
     76                     R.string.application_info_label, null, mUserHandle);
     77             return true;
     78         }
     79     }
     80 
     81     private DimmableIconPreference createRecentLocationEntry(
     82             Drawable icon,
     83             CharSequence label,
     84             boolean isHighBattery,
     85             CharSequence contentDescription,
     86             Preference.OnPreferenceClickListener listener) {
     87         DimmableIconPreference pref = new DimmableIconPreference(mActivity, contentDescription);
     88         pref.setIcon(icon);
     89         pref.setTitle(label);
     90         if (isHighBattery) {
     91             pref.setSummary(R.string.location_high_battery_use);
     92         } else {
     93             pref.setSummary(R.string.location_low_battery_use);
     94         }
     95         pref.setOnPreferenceClickListener(listener);
     96         return pref;
     97     }
     98 
     99     /**
    100      * Fills a list of applications which queried location recently within specified time.
    101      */
    102     public List<Preference> getAppList() {
    103         // Retrieve a location usage list from AppOps
    104         AppOpsManager aoManager =
    105                 (AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE);
    106         List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(new int[] {
    107                 AppOpsManager.OP_MONITOR_LOCATION, AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, });
    108 
    109         // Process the AppOps list and generate a preference list.
    110         ArrayList<Preference> prefs = new ArrayList<Preference>();
    111         final long now = System.currentTimeMillis();
    112         final UserManager um = (UserManager) mActivity.getSystemService(Context.USER_SERVICE);
    113         final List<UserHandle> profiles = um.getUserProfiles();
    114 
    115         final int appOpsN = appOps.size();
    116         for (int i = 0; i < appOpsN; ++i) {
    117             AppOpsManager.PackageOps ops = appOps.get(i);
    118             // Don't show the Android System in the list - it's not actionable for the user.
    119             // Also don't show apps belonging to background users except managed users.
    120             String packageName = ops.getPackageName();
    121             int uid = ops.getUid();
    122             int userId = UserHandle.getUserId(uid);
    123             boolean isAndroidOs =
    124                     (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName);
    125             if (isAndroidOs || !profiles.contains(new UserHandle(userId))) {
    126                 continue;
    127             }
    128             Preference preference = getPreferenceFromOps(um, now, ops);
    129             if (preference != null) {
    130                 prefs.add(preference);
    131             }
    132         }
    133 
    134         return prefs;
    135     }
    136 
    137     /**
    138      * Creates a Preference entry for the given PackageOps.
    139      *
    140      * This method examines the time interval of the PackageOps first. If the PackageOps is older
    141      * than the designated interval, this method ignores the PackageOps object and returns null.
    142      * When the PackageOps is fresh enough, this method returns a Preference pointing to the App
    143      * Info page for that package.
    144      */
    145     private Preference getPreferenceFromOps(final UserManager um, long now,
    146             AppOpsManager.PackageOps ops) {
    147         String packageName = ops.getPackageName();
    148         List<AppOpsManager.OpEntry> entries = ops.getOps();
    149         boolean highBattery = false;
    150         boolean normalBattery = false;
    151         // Earliest time for a location request to end and still be shown in list.
    152         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
    153         for (AppOpsManager.OpEntry entry : entries) {
    154             if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
    155                 switch (entry.getOp()) {
    156                     case AppOpsManager.OP_MONITOR_LOCATION:
    157                         normalBattery = true;
    158                         break;
    159                     case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
    160                         highBattery = true;
    161                         break;
    162                     default:
    163                         break;
    164                 }
    165             }
    166         }
    167 
    168         if (!highBattery && !normalBattery) {
    169             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    170                 Log.v(TAG, packageName + " hadn't used location within the time interval.");
    171             }
    172             return null;
    173         }
    174 
    175         // The package is fresh enough, continue.
    176 
    177         int uid = ops.getUid();
    178         int userId = UserHandle.getUserId(uid);
    179 
    180         DimmableIconPreference preference = null;
    181         try {
    182             IPackageManager ipm = AppGlobals.getPackageManager();
    183             ApplicationInfo appInfo =
    184                     ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
    185             if (appInfo == null) {
    186                 Log.w(TAG, "Null application info retrieved for package " + packageName
    187                         + ", userId " + userId);
    188                 return null;
    189             }
    190             Resources res = mActivity.getResources();
    191 
    192             final UserHandle userHandle = new UserHandle(userId);
    193             Drawable appIcon = mPackageManager.getApplicationIcon(appInfo);
    194             Drawable icon = mPackageManager.getUserBadgedIcon(appIcon, userHandle);
    195             CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
    196             CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
    197             if (appLabel.toString().contentEquals(badgedAppLabel)) {
    198                 // If badged label is not different from original then no need for it as
    199                 // a separate content description.
    200                 badgedAppLabel = null;
    201             }
    202             preference = createRecentLocationEntry(icon,
    203                     appLabel, highBattery, badgedAppLabel,
    204                     new PackageEntryClickedListener(packageName, userHandle));
    205         } catch (RemoteException e) {
    206             Log.w(TAG, "Error while retrieving application info for package " + packageName
    207                     + ", userId " + userId, e);
    208         }
    209 
    210         return preference;
    211     }
    212 }
    213