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.ActivityManager;
     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.graphics.drawable.Drawable;
     25 import android.os.Bundle;
     26 import android.os.Process;
     27 import android.os.UserHandle;
     28 import android.preference.Preference;
     29 import android.preference.PreferenceActivity;
     30 import android.util.Log;
     31 
     32 import com.android.settings.R;
     33 import com.android.settings.applications.InstalledAppDetails;
     34 
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 
     38 /**
     39  * Retrieves the information of applications which accessed location recently.
     40  */
     41 public class RecentLocationApps {
     42     private static final String TAG = RecentLocationApps.class.getSimpleName();
     43     private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
     44 
     45     private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
     46 
     47     private final PreferenceActivity mActivity;
     48     private final PackageManager mPackageManager;
     49 
     50     public RecentLocationApps(PreferenceActivity activity) {
     51         mActivity = activity;
     52         mPackageManager = activity.getPackageManager();
     53     }
     54 
     55     private class PackageEntryClickedListener
     56             implements Preference.OnPreferenceClickListener {
     57         private String mPackage;
     58 
     59         public PackageEntryClickedListener(String packageName) {
     60             mPackage = packageName;
     61         }
     62 
     63         @Override
     64         public boolean onPreferenceClick(Preference preference) {
     65             // start new fragment to display extended information
     66             Bundle args = new Bundle();
     67             args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage);
     68             mActivity.startPreferencePanel(InstalledAppDetails.class.getName(), args,
     69                     R.string.application_info_label, null, null, 0);
     70             return true;
     71         }
     72     }
     73 
     74     private Preference createRecentLocationEntry(
     75             Drawable icon,
     76             CharSequence label,
     77             boolean isHighBattery,
     78             Preference.OnPreferenceClickListener listener) {
     79         Preference pref = new Preference(mActivity);
     80         pref.setIcon(icon);
     81         pref.setTitle(label);
     82         if (isHighBattery) {
     83             pref.setSummary(R.string.location_high_battery_use);
     84         } else {
     85             pref.setSummary(R.string.location_low_battery_use);
     86         }
     87         pref.setOnPreferenceClickListener(listener);
     88         return pref;
     89     }
     90 
     91     /**
     92      * Fills a list of applications which queried location recently within
     93      * specified time.
     94      */
     95     public List<Preference> getAppList() {
     96         // Retrieve a location usage list from AppOps
     97         AppOpsManager aoManager =
     98                 (AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE);
     99         List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(
    100                 new int[] {
    101                     AppOpsManager.OP_MONITOR_LOCATION,
    102                     AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
    103                 });
    104 
    105         // Process the AppOps list and generate a preference list.
    106         ArrayList<Preference> prefs = new ArrayList<Preference>();
    107         long now = System.currentTimeMillis();
    108         for (AppOpsManager.PackageOps ops : appOps) {
    109             // Don't show the Android System in the list - it's not actionable for the user.
    110             // Also don't show apps belonging to background users.
    111             int uid = ops.getUid();
    112             boolean isAndroidOs = (uid == Process.SYSTEM_UID)
    113                     && ANDROID_SYSTEM_PACKAGE_NAME.equals(ops.getPackageName());
    114             if (!isAndroidOs && ActivityManager.getCurrentUser() == UserHandle.getUserId(uid)) {
    115                 Preference pref = getPreferenceFromOps(now, ops);
    116                 if (pref != null) {
    117                     prefs.add(pref);
    118                 }
    119             }
    120         }
    121 
    122         return prefs;
    123     }
    124 
    125     /**
    126      * Creates a Preference entry for the given PackageOps.
    127      *
    128      * This method examines the time interval of the PackageOps first. If the PackageOps is older
    129      * than the designated interval, this method ignores the PackageOps object and returns null.
    130      * When the PackageOps is fresh enough, this method returns a Preference pointing to the App
    131      * Info page for that package.
    132      */
    133     private Preference getPreferenceFromOps(long now, AppOpsManager.PackageOps ops) {
    134         String packageName = ops.getPackageName();
    135         List<AppOpsManager.OpEntry> entries = ops.getOps();
    136         boolean highBattery = false;
    137         boolean normalBattery = false;
    138         // Earliest time for a location request to end and still be shown in list.
    139         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
    140         for (AppOpsManager.OpEntry entry : entries) {
    141             if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
    142                 switch (entry.getOp()) {
    143                     case AppOpsManager.OP_MONITOR_LOCATION:
    144                         normalBattery = true;
    145                         break;
    146                     case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
    147                         highBattery = true;
    148                         break;
    149                     default:
    150                         break;
    151                 }
    152             }
    153         }
    154 
    155         if (!highBattery && !normalBattery) {
    156             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    157                 Log.v(TAG, packageName + " hadn't used location within the time interval.");
    158             }
    159             return null;
    160         }
    161 
    162         // The package is fresh enough, continue.
    163 
    164         Preference pref = null;
    165         try {
    166           ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
    167               packageName, PackageManager.GET_META_DATA);
    168           // Multiple users can install the same package. Each user gets a different Uid for
    169           // the same package.
    170           //
    171           // Here we retrieve the Uid with package name, that will be the Uid for that package
    172           // associated with the current active user. If the Uid differs from the Uid in ops,
    173           // that means this entry belongs to another inactive user and we should ignore that.
    174           if (appInfo.uid == ops.getUid()) {
    175             pref = createRecentLocationEntry(
    176                 mPackageManager.getApplicationIcon(appInfo),
    177                 mPackageManager.getApplicationLabel(appInfo),
    178                 highBattery,
    179                 new PackageEntryClickedListener(packageName));
    180           } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
    181             Log.v(TAG, "package " + packageName + " with Uid " + ops.getUid() +
    182                 " belongs to another inactive account, ignored.");
    183           }
    184         } catch (PackageManager.NameNotFoundException e) {
    185           Log.wtf(TAG, "Package not found: " + packageName, e);
    186         }
    187 
    188         return pref;
    189     }
    190 }
    191