Update gps status icon to be a "high power" location icon.
Move icon to right side of the screen and synchronize status with
AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION.

Change-Id: Iea2570501cb18be0489669fd4ea240dc63f9567a
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b9840e2..0908f36 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -26,7 +26,7 @@
        <item><xliff:g id="id">ime</xliff:g></item>
        <item><xliff:g id="id">sync_failing</xliff:g></item>
        <item><xliff:g id="id">sync_active</xliff:g></item>
-       <item><xliff:g id="id">gps</xliff:g></item>
+       <item><xliff:g id="id">location</xliff:g></item>
        <item><xliff:g id="id">bluetooth</xliff:g></item>
        <item><xliff:g id="id">nfc</xliff:g></item>
        <item><xliff:g id="id">tty</xliff:g></item>
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 989178a..e5f1cf5 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -177,6 +177,17 @@
      */
     public static final String EXTRA_GPS_ENABLED = "enabled";
 
+    /**
+     * Broadcast intent action indicating that a high power location requests
+     * has either started or stopped being active.  The current state of
+     * active location requests should be read from AppOpsManager using
+     * {@code OP_MONITOR_HIGH_POWER_LOCATION}.
+     *
+     * @hide
+     */
+    public static final String HIGH_POWER_REQUEST_CHANGE_ACTION =
+        "android.location.HIGH_POWER_REQUEST_CHANGE";
+
     // Map from LocationListeners to their associated ListenerTransport objects
     private HashMap<LocationListener,ListenerTransport> mListeners =
         new HashMap<LocationListener,ListenerTransport>();
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2267372..5e198a2 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
 
     <!-- Networking and telephony -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_device_access_location_found.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_device_access_location_found.png
new file mode 100644
index 0000000..657a612
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_device_access_location_found.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_device_access_location_found.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_device_access_location_found.png
new file mode 100644
index 0000000..80fc24b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_device_access_location_found.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_device_access_location_found.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_device_access_location_found.png
new file mode 100644
index 0000000..fd8ad64
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_device_access_location_found.png
Binary files differ
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0073e60..33a85c3a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -418,6 +418,9 @@
     <!-- Notification text: when GPS has found a fix [CHAR LIMIT=50] -->
     <string name="gps_notification_found_text">Location set by GPS</string>
 
+    <!-- Accessibility text describing the presence of active location requests by one or more apps -->
+    <string name="accessibility_location_active">Location requests active</string>
+
     <!-- Content description of the clear button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_clear_all">Clear all notifications.</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
index 3f8043d..91ddf0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
@@ -16,10 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
+import android.app.AppOpsManager;
+import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -28,36 +26,39 @@
 import android.database.ContentObserver;
 import android.location.LocationManager;
 import android.os.Handler;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 
 import com.android.systemui.R;
 
 import java.util.ArrayList;
+import java.util.List;
 
+/**
+ * A controller to manage changes of location related states and update the views accordingly.
+ */
 public class LocationController extends BroadcastReceiver {
-    private static final String TAG = "StatusBar.LocationController";
+    // The name of the placeholder corresponding to the location request status icon.
+    // This string corresponds to config_statusBarIcons in core/res/res/values/config.xml.
+    private static final String LOCATION_STATUS_ICON_PLACEHOLDER = "location";
+    private static final int LOCATION_STATUS_ICON_ID
+        = R.drawable.stat_sys_device_access_location_found;
 
-    private static final int GPS_NOTIFICATION_ID = 374203-122084;
+    private static final int[] mHighPowerRequestAppOpArray
+        = new int[] {AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION};
 
     private Context mContext;
 
-    private INotificationManager mNotificationService;
+    private AppOpsManager mAppOpsManager;
+    private StatusBarManager mStatusBarManager;
 
-    private ArrayList<LocationGpsStateChangeCallback> mChangeCallbacks =
-            new ArrayList<LocationGpsStateChangeCallback>();
+    private boolean mAreActiveLocationRequests;
+    private boolean mIsAirplaneMode;
+
     private ArrayList<LocationSettingsChangeCallback> mSettingsChangeCallbacks =
             new ArrayList<LocationSettingsChangeCallback>();
 
     /**
-     * A callback for change in gps status (enabled/disabled, have lock, etc).
-     */
-    public interface LocationGpsStateChangeCallback {
-        public void onLocationGpsStateChanged(boolean inUse, String description);
-    }
-
-    /**
      * A callback for change in location settings (the user has enabled/disabled location).
      */
     public interface LocationSettingsChangeCallback {
@@ -74,13 +75,15 @@
         mContext = context;
 
         IntentFilter filter = new IntentFilter();
-        filter.addAction(LocationManager.GPS_ENABLED_CHANGE_ACTION);
-        filter.addAction(LocationManager.GPS_FIX_CHANGE_ACTION);
+        filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
+        // Listen for a change in the airplane mode setting so we can defensively turn off the
+        // high power location icon when radios are disabled.
+        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         context.registerReceiver(this, filter);
 
-        NotificationManager nm = (NotificationManager)context.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        mNotificationService = nm.getService();
+        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mStatusBarManager
+                = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
 
         // Register to listen for changes to the location settings
         context.getContentResolver().registerContentObserver(
@@ -94,13 +97,11 @@
                         }
                     }
                 });
-    }
 
-    /**
-     * Add a callback to listen for changes in gps status.
-     */
-    public void addStateChangedCallback(LocationGpsStateChangeCallback cb) {
-        mChangeCallbacks.add(cb);
+        // Examine the current location state and initialize the status view.
+        updateActiveLocationRequests();
+        updateAirplaneMode();
+        refreshViews();
     }
 
     /**
@@ -145,76 +146,77 @@
        return isGpsEnabled || isNetworkEnabled;
     }
 
+    /**
+     * Returns true if there currently exist active high power location requests.
+     */
+    private boolean areActiveHighPowerLocationRequests() {
+        List<AppOpsManager.PackageOps> packages
+            = mAppOpsManager.getPackagesForOps(mHighPowerRequestAppOpArray);
+        // AppOpsManager can return null when there is no requested data.
+        if (packages != null) {
+            final int numPackages = packages.size();
+            for (int packageInd = 0; packageInd < numPackages; packageInd++) {
+                AppOpsManager.PackageOps packageOp = packages.get(packageInd);
+                List<AppOpsManager.OpEntry> opEntries = packageOp.getOps();
+                if (opEntries != null) {
+                    final int numOps = opEntries.size();
+                    for (int opInd = 0; opInd < numOps; opInd++) {
+                        AppOpsManager.OpEntry opEntry = opEntries.get(opInd);
+                        // AppOpsManager should only return OP_MONITOR_HIGH_POWER_LOCATION because
+                        // of the mHighPowerRequestAppOpArray filter, but checking defensively.
+                        if (opEntry.getOp() == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION) {
+                            if (opEntry.isRunning()) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    // Updates the status view based on the current state of location requests and airplane mode.
+    private void refreshViews() {
+        // The airplane mode check is defensive - there shouldn't be any active high power
+        // location requests when airplane mode is on.
+        if (!mIsAirplaneMode && mAreActiveLocationRequests) {
+            mStatusBarManager.setIcon(LOCATION_STATUS_ICON_PLACEHOLDER, LOCATION_STATUS_ICON_ID, 0,
+                    mContext.getString(R.string.accessibility_location_active));
+        } else {
+            mStatusBarManager.removeIcon(LOCATION_STATUS_ICON_PLACEHOLDER);
+        }
+    }
+
+    // Reads the active location requests and updates the status view if necessary.
+    private void updateActiveLocationRequests() {
+        boolean hadActiveLocationRequests = mAreActiveLocationRequests;
+        mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
+        if (mAreActiveLocationRequests != hadActiveLocationRequests) {
+            refreshViews();
+        }
+    }
+
+    // Reads the airplane mode setting and updates the status view if necessary.
+    private void updateAirplaneMode() {
+        boolean wasAirplaneMode = mIsAirplaneMode;
+        // TODO This probably warrants a utility method in Settings.java.
+        mIsAirplaneMode = (Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
+        if (mIsAirplaneMode != wasAirplaneMode) {
+            refreshViews();
+        }
+    }
+
     @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
-        final boolean enabled = intent.getBooleanExtra(LocationManager.EXTRA_GPS_ENABLED, false);
-
-        boolean visible;
-        int iconId, textResId;
-
-        if (action.equals(LocationManager.GPS_FIX_CHANGE_ACTION) && enabled) {
-            // GPS is getting fixes
-            iconId = com.android.internal.R.drawable.stat_sys_gps_on;
-            textResId = R.string.gps_notification_found_text;
-            visible = true;
-        } else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) && !enabled) {
-            // GPS is off
-            visible = false;
-            iconId = textResId = 0;
-        } else {
-            // GPS is on, but not receiving fixes
-            iconId = R.drawable.stat_sys_gps_acquiring_anim;
-            textResId = R.string.gps_notification_searching_text;
-            visible = true;
-        }
-
-        try {
-            if (visible) {
-                Intent gpsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
-                gpsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-                PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
-                        gpsIntent, 0, null, UserHandle.CURRENT);
-                String text = mContext.getText(textResId).toString();
-
-                Notification n = new Notification.Builder(mContext)
-                    .setSmallIcon(iconId)
-                    .setContentTitle(text)
-                    .setOngoing(true)
-                    .setContentIntent(pendingIntent)
-                    .getNotification();
-
-                // Notification.Builder will helpfully fill these out for you no matter what you do
-                n.tickerView = null;
-                n.tickerText = null;
-
-                n.priority = Notification.PRIORITY_HIGH;
-
-                int[] idOut = new int[1];
-                mNotificationService.enqueueNotificationWithTag(
-                        mContext.getPackageName(), mContext.getBasePackageName(),
-                        null,
-                        GPS_NOTIFICATION_ID,
-                        n,
-                        idOut,
-                        UserHandle.USER_ALL);
-
-                for (LocationGpsStateChangeCallback cb : mChangeCallbacks) {
-                    cb.onLocationGpsStateChanged(true, text);
-                }
-            } else {
-                mNotificationService.cancelNotificationWithTag(
-                        mContext.getPackageName(), null,
-                        GPS_NOTIFICATION_ID, UserHandle.USER_ALL);
-
-                for (LocationGpsStateChangeCallback cb : mChangeCallbacks) {
-                    cb.onLocationGpsStateChanged(false, null);
-                }
-            }
-        } catch (android.os.RemoteException ex) {
-            // well, it was worth a shot
+        if (LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)) {
+            updateActiveLocationRequests();
+        } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
+            updateAirplaneMode();
         }
     }
 }
-
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index cde84dc..6175268 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -552,8 +552,14 @@
                     allowHighPower = false;
                 }
             }
+            boolean wasHighPowerMonitoring = mOpHighPowerMonitoring;
             mOpHighPowerMonitoring = updateMonitoring(allowHighPower, mOpHighPowerMonitoring,
                     AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION);
+            if (mOpHighPowerMonitoring != wasHighPowerMonitoring) {
+                // send an intent to notify that a high power request has been added/removed.
+                Intent intent = new Intent(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            }
         }
 
         /**