1 /* 2 * Copyright (C) 2015 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 package com.android.permissioncontroller.permission.utils; 17 18 import static android.content.Context.RECEIVER_NOT_EXPORTED; 19 import static android.location.LocationManager.EXTRA_ADAS_GNSS_ENABLED; 20 import static android.location.LocationManager.EXTRA_LOCATION_ENABLED; 21 22 import android.Manifest; 23 import android.app.AlertDialog; 24 import android.content.ActivityNotFoundException; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.DialogInterface.OnClickListener; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.location.LocationManager; 32 import android.os.Build; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.UserHandle; 36 import android.provider.Settings; 37 import android.util.Log; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.RequiresApi; 41 42 import com.android.modules.utils.build.SdkLevel; 43 import com.android.permission.flags.Flags; 44 import com.android.permissioncontroller.PermissionControllerApplication; 45 import com.android.permissioncontroller.R; 46 47 import java.util.ArrayList; 48 import java.util.Collection; 49 import java.util.List; 50 51 public class LocationUtils { 52 53 public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION; 54 public static final String ACTIVITY_RECOGNITION_PERMISSION = 55 Manifest.permission_group.ACTIVITY_RECOGNITION; 56 57 private static final String TAG = LocationUtils.class.getSimpleName(); 58 private static final long LOCATION_UPDATE_DELAY_MS = 1000; 59 private static final Handler sMainHandler = new Handler(Looper.getMainLooper()); 60 showLocationDialog(final Context context, CharSequence label)61 public static void showLocationDialog(final Context context, CharSequence label) { 62 new AlertDialog.Builder(context) 63 .setIcon(R.drawable.ic_dialog_alert_material) 64 .setTitle(android.R.string.dialog_alert_title) 65 .setMessage(context.getString(R.string.location_warning, label)) 66 .setNegativeButton(R.string.ok, null) 67 .setPositiveButton(R.string.location_settings, new OnClickListener() { 68 @Override 69 public void onClick(DialogInterface dialog, int which) { 70 context.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); 71 } 72 }) 73 .show(); 74 } 75 76 /** Start the settings page for the location controller extra package. */ startLocationControllerExtraPackageSettings(@onNull Context context, @NonNull UserHandle user)77 public static void startLocationControllerExtraPackageSettings(@NonNull Context context, 78 @NonNull UserHandle user) { 79 try { 80 context.startActivityAsUser(new Intent( 81 Settings.ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS), user); 82 } catch (ActivityNotFoundException e) { 83 // In rare cases where location controller extra package is set, but 84 // no activity exists to handle the location controller extra package settings 85 // intent, log an error instead of crashing permission controller. 86 Log.e(TAG, "No activity to handle " 87 + "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS"); 88 } 89 } 90 isLocationEnabled(Context context)91 public static boolean isLocationEnabled(Context context) { 92 return context.getSystemService(LocationManager.class).isLocationEnabled(); 93 } 94 95 /** Checks if the automotive location bypass is enabled. */ 96 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) isAutomotiveLocationBypassEnabled(Context context)97 public static boolean isAutomotiveLocationBypassEnabled(Context context) { 98 return context.getSystemService(LocationManager.class).isAdasGnssLocationEnabled(); 99 } 100 101 /** Return the automotive location bypass allowlist. */ 102 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) getAutomotiveLocationBypassAllowlist(Context context)103 public static Collection<String> getAutomotiveLocationBypassAllowlist(Context context) { 104 // TODO(b/335763768): Remove reflection once getAdasAllowlist() is a System API 105 try { 106 LocationManager locationManager = context.getSystemService(LocationManager.class); 107 Object packageTagsList = 108 LocationManager.class.getMethod("getAdasAllowlist").invoke(locationManager); 109 return (Collection<String>) packageTagsList.getClass().getMethod("getPackages") 110 .invoke(packageTagsList); 111 } catch (Exception e) { 112 Log.e(TAG, "Cannot get location bypass allowlist: " + e); 113 return new ArrayList<String>(); 114 } 115 } 116 117 /** Checks if the provided package is an automotive location bypass allowlisted package. */ isAutomotiveLocationBypassAllowlistedPackage( Context context, String packageName)118 public static boolean isAutomotiveLocationBypassAllowlistedPackage( 119 Context context, String packageName) { 120 return SdkLevel.isAtLeastV() && Flags.addBannersToPrivacySensitiveAppsForAaos() 121 && getAutomotiveLocationBypassAllowlist(context).contains(packageName); 122 } 123 124 /** Checks if the provided package is a location provider. */ isLocationProvider(Context context, String packageName)125 public static boolean isLocationProvider(Context context, String packageName) { 126 return context.getSystemService(LocationManager.class).isProviderPackage(packageName); 127 } 128 isLocationGroupAndProvider(Context context, String groupName, String packageName)129 public static boolean isLocationGroupAndProvider(Context context, String groupName, 130 String packageName) { 131 return LOCATION_PERMISSION.equals(groupName) && isLocationProvider(context, packageName); 132 } 133 isLocationGroupAndControllerExtraPackage(@onNull Context context, @NonNull String groupName, @NonNull String packageName)134 public static boolean isLocationGroupAndControllerExtraPackage(@NonNull Context context, 135 @NonNull String groupName, @NonNull String packageName) { 136 return (LOCATION_PERMISSION.equals(groupName) 137 || ACTIVITY_RECOGNITION_PERMISSION.equals(groupName)) 138 && packageName.equals(context.getSystemService(LocationManager.class) 139 .getExtraLocationControllerPackage()); 140 } 141 142 /** Returns whether the location controller extra package is enabled. */ isExtraLocationControllerPackageEnabled(Context context)143 public static boolean isExtraLocationControllerPackageEnabled(Context context) { 144 try { 145 return context.getSystemService(LocationManager.class) 146 .isExtraLocationControllerPackageEnabled(); 147 } catch (Exception e) { 148 return false; 149 } 150 151 } 152 153 /** 154 * A Listener which responds to enabling or disabling of location on the device 155 */ 156 public interface LocationListener { 157 158 /** 159 * A callback run any time we receive a broadcast stating the location enable state has 160 * changed. 161 * @param enabled Whether or not location is enabled 162 */ onLocationStateChange(boolean enabled)163 void onLocationStateChange(boolean enabled); 164 } 165 166 /** 167 * Add a location listener, which will be notified if the automotive location bypass state is 168 * enabled or disabled. 169 * @param listener the listener to add 170 */ 171 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) addAutomotiveLocationBypassListener(LocationListener listener)172 public static void addAutomotiveLocationBypassListener(LocationListener listener) { 173 addLocationListener(listener, sAutomotiveLocationBypassListeners, 174 sAutomotiveLocationBypassBroadcastReceiver, 175 LocationManager.ACTION_ADAS_GNSS_ENABLED_CHANGED); 176 } 177 178 /** 179 * Add a location listener, which will be notified if the main location state is enabled or 180 * disabled. 181 * @param listener the listener to add 182 */ addLocationListener(LocationListener listener)183 public static void addLocationListener(LocationListener listener) { 184 addLocationListener(listener, sLocationListeners, sLocationBroadcastReceiver, 185 LocationManager.MODE_CHANGED_ACTION); 186 } 187 188 /** 189 * Remove an automotive location bypass listener 190 * @param listener The listener to remove 191 * 192 * @return True if it was successfully removed, false otherwise 193 */ removeAutomotiveLocationBypassListener(LocationListener listener)194 public static boolean removeAutomotiveLocationBypassListener(LocationListener listener) { 195 return removeLocationListener(listener, sAutomotiveLocationBypassListeners, 196 sAutomotiveLocationBypassBroadcastReceiver); 197 } 198 199 /** 200 * Remove a main location listener 201 * @param listener The listener to remove 202 * 203 * @return True if it was successfully removed, false otherwise 204 */ removeLocationListener(LocationListener listener)205 public static boolean removeLocationListener(LocationListener listener) { 206 return removeLocationListener(listener, sLocationListeners, sLocationBroadcastReceiver); 207 } 208 209 private static final List<LocationListener> sAutomotiveLocationBypassListeners = 210 new ArrayList<>(); 211 private static final List<LocationListener> sLocationListeners = new ArrayList<>(); 212 213 private static final BroadcastReceiver sAutomotiveLocationBypassBroadcastReceiver = 214 getLocationBroadcastReceiver( 215 SdkLevel.isAtLeastT() ? EXTRA_ADAS_GNSS_ENABLED : EXTRA_LOCATION_ENABLED, 216 sAutomotiveLocationBypassListeners); 217 private static final BroadcastReceiver sLocationBroadcastReceiver = 218 getLocationBroadcastReceiver(EXTRA_LOCATION_ENABLED, sLocationListeners); 219 getLocationBroadcastReceiver(String locationIntentExtra, List<LocationListener> locationListeners)220 private static BroadcastReceiver getLocationBroadcastReceiver(String locationIntentExtra, 221 List<LocationListener> locationListeners) { 222 return new BroadcastReceiver() { 223 @Override 224 public void onReceive(Context context, Intent intent) { 225 boolean isEnabled = intent.getBooleanExtra(locationIntentExtra, true); 226 sMainHandler.postDelayed(() -> { 227 synchronized (locationListeners) { 228 for (LocationListener l : locationListeners) { 229 l.onLocationStateChange(isEnabled); 230 } 231 } 232 }, LOCATION_UPDATE_DELAY_MS); 233 } 234 }; 235 } 236 237 private static void addLocationListener(LocationListener listener, 238 List<LocationListener> locationListeners, BroadcastReceiver locationBroadcastReceiver, 239 String intentAction) { 240 synchronized (locationListeners) { 241 boolean wasEmpty = locationListeners.isEmpty(); 242 locationListeners.add(listener); 243 if (wasEmpty) { 244 IntentFilter intentFilter = new IntentFilter(intentAction); 245 if (SdkLevel.isAtLeastU()) { 246 PermissionControllerApplication.get().getApplicationContext() 247 .registerReceiverForAllUsers(locationBroadcastReceiver, intentFilter, 248 null, null, RECEIVER_NOT_EXPORTED); 249 } else { 250 PermissionControllerApplication.get().getApplicationContext() 251 .registerReceiverForAllUsers(locationBroadcastReceiver, intentFilter, 252 null, null); 253 } 254 } 255 } 256 } 257 258 private static boolean removeLocationListener(LocationListener listener, 259 List<LocationListener> locationListeners, BroadcastReceiver locationBroadcastReceiver) { 260 synchronized (locationListeners) { 261 boolean success = locationListeners.remove(listener); 262 if (success && locationListeners.isEmpty()) { 263 PermissionControllerApplication.get().getApplicationContext() 264 .unregisterReceiver(locationBroadcastReceiver); 265 } 266 return success; 267 } 268 } 269 } 270