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