summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Anil Admal <aadmal@google.com> 2019-05-14 19:14:23 -0700
committer Anil Admal <aadmal@google.com> 2019-05-22 16:06:06 -0700
commit74de73e10eb99cc0f4924f3ae85d7b79a0b69598 (patch)
treea464d12e5df6280b84ce3780e6d9e3311a2dba1c
parent8c9c8a69399348c31edfbcc58a67a859cddde33c (diff)
Use startOp/finishOp to show location icon for non-framework location
Bug: 132738605 Test: Manual Change-Id: Iea4d248bbc50073527503fd06d5d0101864bd662
-rw-r--r--services/core/java/com/android/server/location/GnssVisibilityControl.java176
1 files changed, 144 insertions, 32 deletions
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index b7e0a0fecf00..8d4ad7f30821 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -16,6 +16,7 @@
package com.android.server.location;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.AppOpsManager;
import android.app.Notification;
@@ -27,6 +28,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.location.LocationManager;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
@@ -62,6 +64,9 @@ class GnssVisibilityControl {
// Max wait time for synchronous method onGpsEnabledChanged() to run.
private static final long ON_GPS_ENABLED_CHANGED_TIMEOUT_MILLIS = 3 * 1000;
+ // How long to display location icon for each non-framework non-emergency location request.
+ private static final long LOCATION_ICON_DISPLAY_DURATION_MILLIS = 5 * 1000;
+
// Wakelocks
private static final String WAKELOCK_KEY = TAG;
private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
@@ -77,10 +82,19 @@ class GnssVisibilityControl {
private boolean mIsGpsEnabled;
+ private static final class ProxyAppState {
+ private boolean mHasLocationPermission;
+ private boolean mIsLocationIconOn;
+
+ private ProxyAppState(boolean hasLocationPermission) {
+ mHasLocationPermission = hasLocationPermission;
+ }
+ }
+
// Number of non-framework location access proxy apps is expected to be small (< 5).
- private static final int ARRAY_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7;
- private ArrayMap<String, Boolean> mProxyAppToLocationPermissions = new ArrayMap<>(
- ARRAY_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS);
+ private static final int ARRAY_MAP_INITIAL_CAPACITY_PROXY_APPS_STATE = 5;
+ private ArrayMap<String, ProxyAppState> mProxyAppsState = new ArrayMap<>(
+ ARRAY_MAP_INITIAL_CAPACITY_PROXY_APPS_STATE);
private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener =
uid -> runOnHandler(() -> handlePermissionsChanged(uid));
@@ -173,18 +187,18 @@ class GnssVisibilityControl {
}
private void handleProxyAppPackageUpdate(String pkgName, String action) {
- final Boolean locationPermission = mProxyAppToLocationPermissions.get(pkgName);
- if (locationPermission == null) {
+ final ProxyAppState proxyAppState = mProxyAppsState.get(pkgName);
+ if (proxyAppState == null) {
return; // ignore, pkgName is not one of the proxy apps in our list.
}
if (DEBUG) Log.d(TAG, "Proxy app " + pkgName + " package changed: " + action);
final boolean updatedLocationPermission = shouldEnableLocationPermissionInGnssHal(pkgName);
- if (locationPermission != updatedLocationPermission) {
+ if (proxyAppState.mHasLocationPermission != updatedLocationPermission) {
// Permission changed. So, update the GNSS HAL with the updated list.
Log.i(TAG, "Proxy app " + pkgName + " location permission changed."
+ " IsLocationPermissionEnabled: " + updatedLocationPermission);
- mProxyAppToLocationPermissions.put(pkgName, updatedLocationPermission);
+ proxyAppState.mHasLocationPermission = updatedLocationPermission;
updateNfwLocationAccessProxyAppsInGnssHal();
}
}
@@ -196,35 +210,53 @@ class GnssVisibilityControl {
if (nfwLocationAccessProxyApps.isEmpty()) {
// Stop listening for app permission changes. Clear the app list in GNSS HAL.
- if (!mProxyAppToLocationPermissions.isEmpty()) {
+ if (!mProxyAppsState.isEmpty()) {
mPackageManager.removeOnPermissionsChangeListener(mOnPermissionsChangedListener);
- mProxyAppToLocationPermissions.clear();
+ resetProxyAppsState();
updateNfwLocationAccessProxyAppsInGnssHal();
}
return;
}
- if (mProxyAppToLocationPermissions.isEmpty()) {
+ if (mProxyAppsState.isEmpty()) {
mPackageManager.addOnPermissionsChangeListener(mOnPermissionsChangedListener);
} else {
- mProxyAppToLocationPermissions.clear();
+ resetProxyAppsState();
}
for (String proxyAppPkgName : nfwLocationAccessProxyApps) {
- mProxyAppToLocationPermissions.put(proxyAppPkgName,
- shouldEnableLocationPermissionInGnssHal(proxyAppPkgName));
+ ProxyAppState proxyAppState = new ProxyAppState(shouldEnableLocationPermissionInGnssHal(
+ proxyAppPkgName));
+ mProxyAppsState.put(proxyAppPkgName, proxyAppState);
}
updateNfwLocationAccessProxyAppsInGnssHal();
}
+ private void resetProxyAppsState() {
+ // Clear location icons displayed.
+ for (Map.Entry<String, ProxyAppState> entry : mProxyAppsState.entrySet()) {
+ ProxyAppState proxyAppState = entry.getValue();
+ if (!proxyAppState.mIsLocationIconOn) {
+ continue;
+ }
+
+ mHandler.removeCallbacksAndMessages(proxyAppState);
+ final ApplicationInfo proxyAppInfo = getProxyAppInfo(entry.getKey());
+ if (proxyAppInfo != null) {
+ clearLocationIcon(proxyAppState, proxyAppInfo.uid, entry.getKey());
+ }
+ }
+ mProxyAppsState.clear();
+ }
+
private boolean isProxyAppListUpdated(List<String> nfwLocationAccessProxyApps) {
- if (nfwLocationAccessProxyApps.size() != mProxyAppToLocationPermissions.size()) {
+ if (nfwLocationAccessProxyApps.size() != mProxyAppsState.size()) {
return true;
}
for (String nfwLocationAccessProxyApp : nfwLocationAccessProxyApps) {
- if (!mProxyAppToLocationPermissions.containsKey(nfwLocationAccessProxyApp)) {
+ if (!mProxyAppsState.containsKey(nfwLocationAccessProxyApp)) {
return true;
}
}
@@ -326,11 +358,11 @@ class GnssVisibilityControl {
}
private void handlePermissionsChanged(int uid) {
- if (mProxyAppToLocationPermissions.isEmpty()) {
+ if (mProxyAppsState.isEmpty()) {
return;
}
- for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) {
+ for (Map.Entry<String, ProxyAppState> entry : mProxyAppsState.entrySet()) {
final String proxyAppPkgName = entry.getKey();
final ApplicationInfo proxyAppInfo = getProxyAppInfo(proxyAppPkgName);
if (proxyAppInfo == null || proxyAppInfo.uid != uid) {
@@ -339,10 +371,11 @@ class GnssVisibilityControl {
final boolean isLocationPermissionEnabled = shouldEnableLocationPermissionInGnssHal(
proxyAppPkgName);
- if (isLocationPermissionEnabled != entry.getValue()) {
+ ProxyAppState proxyAppState = entry.getValue();
+ if (isLocationPermissionEnabled != proxyAppState.mHasLocationPermission) {
Log.i(TAG, "Proxy app " + proxyAppPkgName + " location permission changed."
+ " IsLocationPermissionEnabled: " + isLocationPermissionEnabled);
- entry.setValue(isLocationPermissionEnabled);
+ proxyAppState.mHasLocationPermission = isLocationPermissionEnabled;
updateNfwLocationAccessProxyAppsInGnssHal();
}
return;
@@ -394,8 +427,8 @@ class GnssVisibilityControl {
private String[] getLocationPermissionEnabledProxyApps() {
// Get a count of proxy apps with location permission enabled for array creation size.
int countLocationPermissionEnabledProxyApps = 0;
- for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) {
- if (hasLocationPermissionEnabled) {
+ for (ProxyAppState proxyAppState : mProxyAppsState.values()) {
+ if (proxyAppState.mHasLocationPermission) {
++countLocationPermissionEnabledProxyApps;
}
}
@@ -403,10 +436,9 @@ class GnssVisibilityControl {
int i = 0;
String[] locationPermissionEnabledProxyApps =
new String[countLocationPermissionEnabledProxyApps];
- for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) {
+ for (Map.Entry<String, ProxyAppState> entry : mProxyAppsState.entrySet()) {
final String proxyApp = entry.getKey();
- final boolean hasLocationPermissionEnabled = entry.getValue();
- if (hasLocationPermissionEnabled) {
+ if (entry.getValue().mHasLocationPermission) {
locationPermissionEnabledProxyApps[i++] = proxyApp;
}
}
@@ -422,12 +454,11 @@ class GnssVisibilityControl {
}
final String proxyAppPkgName = nfwNotification.mProxyAppPackageName;
- final Boolean isLocationPermissionEnabled = mProxyAppToLocationPermissions.get(
- proxyAppPkgName);
+ final ProxyAppState proxyAppState = mProxyAppsState.get(proxyAppPkgName);
final boolean isLocationRequestAccepted = nfwNotification.isRequestAccepted();
final boolean isPermissionMismatched =
- (isLocationPermissionEnabled == null) ? isLocationRequestAccepted
- : (isLocationPermissionEnabled != isLocationRequestAccepted);
+ (proxyAppState == null) ? isLocationRequestAccepted
+ : (proxyAppState.mHasLocationPermission != isLocationRequestAccepted);
logEvent(nfwNotification, isPermissionMismatched);
if (!nfwNotification.isRequestAttributedToProxyApp()) {
@@ -442,7 +473,7 @@ class GnssVisibilityControl {
if (DEBUG) {
Log.d(TAG, "Non-framework location request rejected. ProxyAppPackageName field"
+ " is not set in the notification: " + nfwNotification + ". Number of"
- + " configured proxy apps: " + mProxyAppToLocationPermissions.size());
+ + " configured proxy apps: " + mProxyAppsState.size());
}
return;
}
@@ -452,7 +483,7 @@ class GnssVisibilityControl {
return;
}
- if (isLocationPermissionEnabled == null) {
+ if (proxyAppState == null) {
Log.w(TAG, "Could not find proxy app " + proxyAppPkgName + " in the value specified for"
+ " config parameter: " + GnssConfiguration.CONFIG_NFW_PROXY_APPS
+ ". AppOps service not notified for notification: " + nfwNotification);
@@ -467,18 +498,99 @@ class GnssVisibilityControl {
return;
}
- mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, proxyAppInfo.uid, proxyAppPkgName);
+ if (nfwNotification.isLocationProvided()) {
+ showLocationIcon(proxyAppState, nfwNotification, proxyAppInfo.uid, proxyAppPkgName);
+ mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, proxyAppInfo.uid,
+ proxyAppPkgName);
+ }
// Log proxy app permission mismatch between framework and GNSS HAL.
if (isPermissionMismatched) {
Log.w(TAG, "Permission mismatch. Framework proxy app " + proxyAppPkgName
- + " location permission is set to " + isLocationPermissionEnabled
+ + " location permission is set to " + proxyAppState.mHasLocationPermission
+ " but GNSS non-framework location access response type is "
+ nfwNotification.getResponseTypeAsString() + " for notification: "
+ nfwNotification);
}
}
+ private void showLocationIcon(ProxyAppState proxyAppState, NfwNotification nfwNotification,
+ int uid, String proxyAppPkgName) {
+ // If we receive a new NfwNotification before the location icon is turned off for the
+ // previous notification, update the timer to extend the location icon display duration.
+ final boolean isLocationIconOn = proxyAppState.mIsLocationIconOn;
+ if (!isLocationIconOn) {
+ if (!updateLocationIcon(/* displayLocationIcon = */ true, uid, proxyAppPkgName)) {
+ Log.w(TAG, "Failed to show Location icon for notification: " + nfwNotification);
+ return;
+ }
+ proxyAppState.mIsLocationIconOn = true;
+ } else {
+ // Extend timer by canceling the current one and starting a new one.
+ mHandler.removeCallbacksAndMessages(proxyAppState);
+ }
+
+ // Start timer to turn off location icon. proxyAppState is used as a token to cancel timer.
+ if (DEBUG) {
+ Log.d(TAG, "Location icon on. " + (isLocationIconOn ? "Extending" : "Setting")
+ + " icon display timer. Uid: " + uid + ", proxyAppPkgName: " + proxyAppPkgName);
+ }
+ if (!mHandler.postDelayed(() -> handleLocationIconTimeout(proxyAppPkgName),
+ /* token = */ proxyAppState, LOCATION_ICON_DISPLAY_DURATION_MILLIS)) {
+ clearLocationIcon(proxyAppState, uid, proxyAppPkgName);
+ Log.w(TAG, "Failed to show location icon for the full duration for notification: "
+ + nfwNotification);
+ }
+ }
+
+ private void handleLocationIconTimeout(String proxyAppPkgName) {
+ // Get uid again instead of using the one provided in startOp() call as the app could have
+ // been uninstalled and reinstalled during the timeout duration (unlikely in real world).
+ final ApplicationInfo proxyAppInfo = getProxyAppInfo(proxyAppPkgName);
+ if (proxyAppInfo != null) {
+ clearLocationIcon(mProxyAppsState.get(proxyAppPkgName), proxyAppInfo.uid,
+ proxyAppPkgName);
+ }
+ }
+
+ private void clearLocationIcon(@Nullable ProxyAppState proxyAppState, int uid,
+ String proxyAppPkgName) {
+ updateLocationIcon(/* displayLocationIcon = */ false, uid, proxyAppPkgName);
+ if (proxyAppState != null) proxyAppState.mIsLocationIconOn = false;
+ if (DEBUG) {
+ Log.d(TAG, "Location icon off. Uid: " + uid + ", proxyAppPkgName: " + proxyAppPkgName);
+ }
+ }
+
+ private boolean updateLocationIcon(boolean displayLocationIcon, int uid,
+ String proxyAppPkgName) {
+ if (displayLocationIcon) {
+ // Need two calls to startOp() here with different op code so that the proxy app shows
+ // up in the recent location requests page and also the location icon gets displayed.
+ if (mAppOps.startOpNoThrow(AppOpsManager.OP_MONITOR_LOCATION, uid,
+ proxyAppPkgName) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ if (mAppOps.startOpNoThrow(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, uid,
+ proxyAppPkgName) != AppOpsManager.MODE_ALLOWED) {
+ mAppOps.finishOp(AppOpsManager.OP_MONITOR_LOCATION, uid, proxyAppPkgName);
+ return false;
+ }
+ } else {
+ mAppOps.finishOp(AppOpsManager.OP_MONITOR_LOCATION, uid, proxyAppPkgName);
+ mAppOps.finishOp(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, uid, proxyAppPkgName);
+ }
+ sendHighPowerMonitoringBroadcast();
+ return true;
+ }
+
+ private void sendHighPowerMonitoringBroadcast() {
+ // Send an intent to notify that a high power request has been added/removed so that
+ // the SystemUi checks the state of AppOps and updates the location icon accordingly.
+ Intent intent = new Intent(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
private void handleEmergencyNfwNotification(NfwNotification nfwNotification) {
boolean isPermissionMismatched = false;
if (!nfwNotification.isRequestAccepted()) {