LocationController.java revision c750c1fb83fbdec895e236dda7207db4da14ec49
1/*
2 * Copyright (C) 2008 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
17package com.android.systemui.statusbar.policy;
18
19import android.app.AppOpsManager;
20import android.app.StatusBarManager;
21import android.content.BroadcastReceiver;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.database.ContentObserver;
27import android.location.LocationManager;
28import android.os.Handler;
29import android.os.UserManager;
30import android.provider.Settings;
31
32import com.android.systemui.R;
33
34import java.util.ArrayList;
35import java.util.List;
36
37/**
38 * A controller to manage changes of location related states and update the views accordingly.
39 */
40public class LocationController extends BroadcastReceiver {
41    // The name of the placeholder corresponding to the location request status icon.
42    // This string corresponds to config_statusBarIcons in core/res/res/values/config.xml.
43    private static final String LOCATION_STATUS_ICON_PLACEHOLDER = "location";
44    private static final int LOCATION_STATUS_ICON_ID
45        = R.drawable.stat_sys_device_access_location_found;
46
47    private static final int[] mHighPowerRequestAppOpArray
48        = new int[] {AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION};
49
50    private Context mContext;
51
52    private AppOpsManager mAppOpsManager;
53    private StatusBarManager mStatusBarManager;
54
55    private boolean mAreActiveLocationRequests;
56    private boolean mIsAirplaneMode;
57
58    private ArrayList<LocationSettingsChangeCallback> mSettingsChangeCallbacks =
59            new ArrayList<LocationSettingsChangeCallback>();
60
61    /**
62     * A callback for change in location settings (the user has enabled/disabled location).
63     */
64    public interface LocationSettingsChangeCallback {
65        /**
66         * Called whenever location settings change.
67         *
68         * @param locationEnabled A value of true indicates that at least one type of location
69         *                        is enabled in settings.
70         */
71        public void onLocationSettingsChanged(boolean locationEnabled);
72    }
73
74    public LocationController(Context context) {
75        mContext = context;
76
77        IntentFilter filter = new IntentFilter();
78        filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
79        // Listen for a change in the airplane mode setting so we can defensively turn off the
80        // high power location icon when radios are disabled.
81        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
82        context.registerReceiver(this, filter);
83
84        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
85        mStatusBarManager
86                = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
87
88        // Register to listen for changes to the location settings
89        context.getContentResolver().registerContentObserver(
90                Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
91                new ContentObserver(new Handler()) {
92                    @Override
93                    public void onChange(boolean selfChange) {
94                        boolean isEnabled = isLocationEnabled();
95                        for (LocationSettingsChangeCallback cb : mSettingsChangeCallbacks) {
96                            cb.onLocationSettingsChanged(isEnabled);
97                        }
98                    }
99                });
100
101        // Examine the current location state and initialize the status view.
102        updateActiveLocationRequests();
103        updateAirplaneMode();
104        refreshViews();
105    }
106
107    /**
108     * Add a callback to listen for changes in location settings.
109     */
110    public void addSettingsChangedCallback(LocationSettingsChangeCallback cb) {
111        mSettingsChangeCallbacks.add(cb);
112    }
113
114    /**
115     * Enable or disable location in settings.
116     *
117     * <p>This will attempt to enable/disable every type of location setting
118     * (e.g. high and balanced power).
119     *
120     * <p>If enabling, a user consent dialog will pop up prompting the user to accept.
121     * If the user doesn't accept, network location won't be enabled.
122     */
123    public void setLocationEnabled(boolean enabled) {
124        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
125        if (um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) {
126            return;
127        }
128        final ContentResolver cr = mContext.getContentResolver();
129        Settings.Secure.setLocationProviderEnabled(
130                cr, LocationManager.GPS_PROVIDER, enabled);
131        // When enabling the NETWORK_PROVIDER, a user consent dialog will pop up, and the
132        // setting won't actually be enabled until the user accepts the agreement.
133        Settings.Secure.setLocationProviderEnabled(
134                cr, LocationManager.NETWORK_PROVIDER, enabled);
135    }
136
137    /**
138     * Returns true if either gps or network location are enabled in settings.
139     */
140    public boolean isLocationEnabled() {
141        ContentResolver contentResolver = mContext.getContentResolver();
142        boolean isGpsEnabled = Settings.Secure.isLocationProviderEnabled(
143                contentResolver, LocationManager.GPS_PROVIDER);
144        boolean isNetworkEnabled = Settings.Secure.isLocationProviderEnabled(
145                contentResolver, LocationManager.NETWORK_PROVIDER);
146       return isGpsEnabled || isNetworkEnabled;
147    }
148
149    /**
150     * Returns true if there currently exist active high power location requests.
151     */
152    private boolean areActiveHighPowerLocationRequests() {
153        List<AppOpsManager.PackageOps> packages
154            = mAppOpsManager.getPackagesForOps(mHighPowerRequestAppOpArray);
155        // AppOpsManager can return null when there is no requested data.
156        if (packages != null) {
157            final int numPackages = packages.size();
158            for (int packageInd = 0; packageInd < numPackages; packageInd++) {
159                AppOpsManager.PackageOps packageOp = packages.get(packageInd);
160                List<AppOpsManager.OpEntry> opEntries = packageOp.getOps();
161                if (opEntries != null) {
162                    final int numOps = opEntries.size();
163                    for (int opInd = 0; opInd < numOps; opInd++) {
164                        AppOpsManager.OpEntry opEntry = opEntries.get(opInd);
165                        // AppOpsManager should only return OP_MONITOR_HIGH_POWER_LOCATION because
166                        // of the mHighPowerRequestAppOpArray filter, but checking defensively.
167                        if (opEntry.getOp() == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION) {
168                            if (opEntry.isRunning()) {
169                                return true;
170                            }
171                        }
172                    }
173                }
174            }
175        }
176
177        return false;
178    }
179
180    // Updates the status view based on the current state of location requests and airplane mode.
181    private void refreshViews() {
182        // The airplane mode check is defensive - there shouldn't be any active high power
183        // location requests when airplane mode is on.
184        if (!mIsAirplaneMode && mAreActiveLocationRequests) {
185            mStatusBarManager.setIcon(LOCATION_STATUS_ICON_PLACEHOLDER, LOCATION_STATUS_ICON_ID, 0,
186                    mContext.getString(R.string.accessibility_location_active));
187        } else {
188            mStatusBarManager.removeIcon(LOCATION_STATUS_ICON_PLACEHOLDER);
189        }
190    }
191
192    // Reads the active location requests and updates the status view if necessary.
193    private void updateActiveLocationRequests() {
194        boolean hadActiveLocationRequests = mAreActiveLocationRequests;
195        mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
196        if (mAreActiveLocationRequests != hadActiveLocationRequests) {
197            refreshViews();
198        }
199    }
200
201    // Reads the airplane mode setting and updates the status view if necessary.
202    private void updateAirplaneMode() {
203        boolean wasAirplaneMode = mIsAirplaneMode;
204        // TODO This probably warrants a utility method in Settings.java.
205        mIsAirplaneMode = (Settings.Global.getInt(
206                mContext.getContentResolver(),
207                Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
208        if (mIsAirplaneMode != wasAirplaneMode) {
209            refreshViews();
210        }
211    }
212
213    @Override
214    public void onReceive(Context context, Intent intent) {
215        final String action = intent.getAction();
216        if (LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)) {
217            updateActiveLocationRequests();
218        } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
219            updateAirplaneMode();
220        }
221    }
222}
223