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