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