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.location.LocationManager.EXTRA_LOCATION_ENABLED;
19 
20 import android.Manifest;
21 import android.app.AlertDialog;
22 import android.content.ActivityNotFoundException;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.DialogInterface.OnClickListener;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.location.LocationManager;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.util.Log;
35 
36 import androidx.annotation.NonNull;
37 
38 import com.android.permissioncontroller.PermissionControllerApplication;
39 import com.android.permissioncontroller.R;
40 
41 import java.util.ArrayList;
42 
43 public class LocationUtils {
44 
45     public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION;
46 
47     private static final String TAG = LocationUtils.class.getSimpleName();
48     private static final long LOCATION_UPDATE_DELAY_MS = 1000;
49     private static final Handler sMainHandler = new Handler(Looper.getMainLooper());
50 
showLocationDialog(final Context context, CharSequence label)51     public static void showLocationDialog(final Context context, CharSequence label) {
52         new AlertDialog.Builder(context)
53                 .setIcon(R.drawable.ic_dialog_alert_material)
54                 .setTitle(android.R.string.dialog_alert_title)
55                 .setMessage(context.getString(R.string.location_warning, label))
56                 .setNegativeButton(R.string.ok, null)
57                 .setPositiveButton(R.string.location_settings, new OnClickListener() {
58                     @Override
59                     public void onClick(DialogInterface dialog, int which) {
60                         context.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
61                     }
62                 })
63                 .show();
64     }
65 
66     /** Start the settings page for the location controller extra package. */
startLocationControllerExtraPackageSettings(@onNull Context context, @NonNull UserHandle user)67     public static void startLocationControllerExtraPackageSettings(@NonNull Context context,
68             @NonNull UserHandle user) {
69         try {
70             context.startActivityAsUser(new Intent(
71                         Settings.ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS), user);
72         } catch (ActivityNotFoundException e) {
73             // In rare cases where location controller extra package is set, but
74             // no activity exists to handle the location controller extra package settings
75             // intent, log an error instead of crashing permission controller.
76             Log.e(TAG, "No activity to handle "
77                         + "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS");
78         }
79     }
80 
isLocationEnabled(Context context)81     public static boolean isLocationEnabled(Context context) {
82         return context.getSystemService(LocationManager.class).isLocationEnabled();
83     }
84 
isLocationGroupAndProvider(Context context, String groupName, String packageName)85     public static boolean isLocationGroupAndProvider(Context context, String groupName,
86             String packageName) {
87         return LOCATION_PERMISSION.equals(groupName)
88                 && context.getSystemService(LocationManager.class).isProviderPackage(packageName);
89     }
90 
isLocationGroupAndControllerExtraPackage(@onNull Context context, @NonNull String groupName, @NonNull String packageName)91     public static boolean isLocationGroupAndControllerExtraPackage(@NonNull Context context,
92             @NonNull String groupName, @NonNull String packageName) {
93         return LOCATION_PERMISSION.equals(groupName)
94                 && packageName.equals(context.getSystemService(LocationManager.class)
95                         .getExtraLocationControllerPackage());
96     }
97 
98     /** Returns whether the location controller extra package is enabled. */
isExtraLocationControllerPackageEnabled(Context context)99     public static boolean isExtraLocationControllerPackageEnabled(Context context) {
100         try {
101             return context.getSystemService(LocationManager.class)
102                     .isExtraLocationControllerPackageEnabled();
103         } catch (Exception e) {
104             return false;
105         }
106 
107     }
108 
109     /**
110      * A Listener which responds to enabling or disabling of location on the device
111      */
112     public interface LocationListener {
113 
114         /**
115          * A callback run any time we receive a broadcast stating the location enable state has
116          * changed.
117          * @param enabled Whether or not location is enabled
118          */
onLocationStateChange(boolean enabled)119         void onLocationStateChange(boolean enabled);
120     }
121 
122     private static final ArrayList<LocationListener> sLocationListeners = new ArrayList<>();
123 
124     private static BroadcastReceiver sLocationBroadcastReceiver = new BroadcastReceiver() {
125         @Override
126         public void onReceive(Context context, Intent intent) {
127             boolean isEnabled = intent.getBooleanExtra(EXTRA_LOCATION_ENABLED, true);
128             sMainHandler.postDelayed(() -> {
129                 synchronized (sLocationListeners) {
130                     for (LocationListener l : sLocationListeners) {
131                         l.onLocationStateChange(isEnabled);
132                     }
133                 }
134             }, LOCATION_UPDATE_DELAY_MS);
135         }
136     };
137 
138     /**
139      * Add a LocationListener, which will be notified if the location provider is enabled or
140      * disabled
141      * @param listener the listener to add
142      */
addLocationListener(LocationListener listener)143     public static void addLocationListener(LocationListener listener) {
144         synchronized (sLocationListeners) {
145             boolean wasEmpty = sLocationListeners.isEmpty();
146             sLocationListeners.add(listener);
147             if (wasEmpty) {
148                 IntentFilter intentFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
149                 PermissionControllerApplication.get().getApplicationContext()
150                         .registerReceiverForAllUsers(sLocationBroadcastReceiver, intentFilter,
151                                 null, null);
152             }
153         }
154     }
155 
156     /**
157      * Remove a LocationListener
158      * @param listener The listener to remove
159      *
160      * @return True if it was successfully removed, false otherwise
161      */
removeLocationListener(LocationListener listener)162     public static boolean removeLocationListener(LocationListener listener) {
163         synchronized (sLocationListeners) {
164             boolean success = sLocationListeners.remove(listener);
165             if (success && sLocationListeners.isEmpty()) {
166                 PermissionControllerApplication.get().getApplicationContext()
167                         .unregisterReceiver(sLocationBroadcastReceiver);
168             }
169             return success;
170         }
171     }
172 }
173