1/*
2 * Copyright (C) 2011 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.settings.location;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.location.LocationManager;
26import android.location.SettingInjectorService;
27import android.os.Bundle;
28import android.preference.Preference;
29import android.preference.PreferenceActivity;
30import android.preference.PreferenceCategory;
31import android.preference.PreferenceGroup;
32import android.preference.PreferenceScreen;
33import android.provider.Settings;
34import android.util.Log;
35import android.view.Gravity;
36import android.widget.CompoundButton;
37import android.widget.Switch;
38
39import com.android.settings.R;
40
41import java.util.Collections;
42import java.util.Comparator;
43import java.util.List;
44
45/**
46 * Location access settings.
47 */
48public class LocationSettings extends LocationSettingsBase
49        implements CompoundButton.OnCheckedChangeListener {
50
51    private static final String TAG = "LocationSettings";
52
53    /** Key for preference screen "Mode" */
54    private static final String KEY_LOCATION_MODE = "location_mode";
55    /** Key for preference category "Recent location requests" */
56    private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests";
57    /** Key for preference category "Location services" */
58    private static final String KEY_LOCATION_SERVICES = "location_services";
59
60    private Switch mSwitch;
61    private boolean mValidListener;
62    private Preference mLocationMode;
63    private PreferenceCategory mCategoryRecentLocationRequests;
64    /** Receives UPDATE_INTENT  */
65    private BroadcastReceiver mReceiver;
66
67    public LocationSettings() {
68        mValidListener = false;
69    }
70
71    @Override
72    public void onResume() {
73        super.onResume();
74        mSwitch = new Switch(getActivity());
75        mSwitch.setOnCheckedChangeListener(this);
76        mValidListener = true;
77        createPreferenceHierarchy();
78    }
79
80    @Override
81    public void onPause() {
82        try {
83            getActivity().unregisterReceiver(mReceiver);
84        } catch (RuntimeException e) {
85            // Ignore exceptions caused by race condition
86        }
87        super.onPause();
88        mValidListener = false;
89        mSwitch.setOnCheckedChangeListener(null);
90    }
91
92    private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) {
93        // If there's some items to display, sort the items and add them to the container.
94        Collections.sort(prefs, new Comparator<Preference>() {
95            @Override
96            public int compare(Preference lhs, Preference rhs) {
97                return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
98            }
99        });
100        for (Preference entry : prefs) {
101            container.addPreference(entry);
102        }
103    }
104
105    private PreferenceScreen createPreferenceHierarchy() {
106        final PreferenceActivity activity = (PreferenceActivity) getActivity();
107        PreferenceScreen root = getPreferenceScreen();
108        if (root != null) {
109            root.removeAll();
110        }
111        addPreferencesFromResource(R.xml.location_settings);
112        root = getPreferenceScreen();
113
114        mLocationMode = root.findPreference(KEY_LOCATION_MODE);
115        mLocationMode.setOnPreferenceClickListener(
116                new Preference.OnPreferenceClickListener() {
117                    @Override
118                    public boolean onPreferenceClick(Preference preference) {
119                        activity.startPreferencePanel(
120                                LocationMode.class.getName(), null,
121                                R.string.location_mode_screen_title, null, LocationSettings.this,
122                                0);
123                        return true;
124                    }
125                });
126
127        mCategoryRecentLocationRequests =
128                (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS);
129        RecentLocationApps recentApps = new RecentLocationApps(activity);
130        List<Preference> recentLocationRequests = recentApps.getAppList();
131        if (recentLocationRequests.size() > 0) {
132            addPreferencesSorted(recentLocationRequests, mCategoryRecentLocationRequests);
133        } else {
134            // If there's no item to display, add a "No recent apps" item.
135            Preference banner = new Preference(activity);
136            banner.setLayoutResource(R.layout.location_list_no_item);
137            banner.setTitle(R.string.location_no_recent_apps);
138            banner.setSelectable(false);
139            mCategoryRecentLocationRequests.addPreference(banner);
140        }
141
142        addLocationServices(activity, root);
143
144        // Only show the master switch when we're not in multi-pane mode, and not being used as
145        // Setup Wizard.
146        if (activity.onIsHidingHeaders() || !activity.onIsMultiPane()) {
147            final int padding = activity.getResources().getDimensionPixelSize(
148                    R.dimen.action_bar_switch_padding);
149            mSwitch.setPaddingRelative(0, 0, padding, 0);
150            activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
151                    ActionBar.DISPLAY_SHOW_CUSTOM);
152            activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams(
153                    ActionBar.LayoutParams.WRAP_CONTENT,
154                    ActionBar.LayoutParams.WRAP_CONTENT,
155                    Gravity.CENTER_VERTICAL | Gravity.END));
156        }
157
158        setHasOptionsMenu(true);
159
160        refreshLocationMode();
161        return root;
162    }
163
164    /**
165     * Add the settings injected by external apps into the "App Settings" category. Hides the
166     * category if there are no injected settings.
167     *
168     * Reloads the settings whenever receives
169     * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}. As a safety measure,
170     * also reloads on {@link LocationManager#MODE_CHANGED_ACTION} to ensure the settings are
171     * up-to-date after mode changes even if an affected app doesn't send the setting changed
172     * broadcast.
173     */
174    private void addLocationServices(Context context, PreferenceScreen root) {
175        PreferenceCategory categoryLocationServices =
176                (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES);
177        final SettingsInjector injector = new SettingsInjector(context);
178        List<Preference> locationServices = injector.getInjectedSettings();
179
180        mReceiver = new BroadcastReceiver() {
181            @Override
182            public void onReceive(Context context, Intent intent) {
183                if (Log.isLoggable(TAG, Log.DEBUG)) {
184                    Log.d(TAG, "Received settings change intent: " + intent);
185                }
186                injector.reloadStatusMessages();
187            }
188        };
189
190        IntentFilter filter = new IntentFilter();
191        filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
192        filter.addAction(LocationManager.MODE_CHANGED_ACTION);
193        context.registerReceiver(mReceiver, filter);
194
195        if (locationServices.size() > 0) {
196            addPreferencesSorted(locationServices, categoryLocationServices);
197        } else {
198            // If there's no item to display, remove the whole category.
199            root.removePreference(categoryLocationServices);
200        }
201    }
202
203    @Override
204    public int getHelpResource() {
205        return R.string.help_url_location_access;
206    }
207
208    @Override
209    public void onModeChanged(int mode, boolean restricted) {
210        switch (mode) {
211            case Settings.Secure.LOCATION_MODE_OFF:
212                mLocationMode.setSummary(R.string.location_mode_location_off_title);
213                break;
214            case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
215                mLocationMode.setSummary(R.string.location_mode_sensors_only_title);
216                break;
217            case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
218                mLocationMode.setSummary(R.string.location_mode_battery_saving_title);
219                break;
220            case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
221                mLocationMode.setSummary(R.string.location_mode_high_accuracy_title);
222                break;
223            default:
224                break;
225        }
226
227        // Restricted user can't change the location mode, so disable the master switch. But in some
228        // corner cases, the location might still be enabled. In such case the master switch should
229        // be disabled but checked.
230        boolean enabled = (mode != Settings.Secure.LOCATION_MODE_OFF);
231        mSwitch.setEnabled(!restricted);
232        mLocationMode.setEnabled(enabled && !restricted);
233        mCategoryRecentLocationRequests.setEnabled(enabled);
234
235        if (enabled != mSwitch.isChecked()) {
236            // set listener to null so that that code below doesn't trigger onCheckedChanged()
237            if (mValidListener) {
238                mSwitch.setOnCheckedChangeListener(null);
239            }
240            mSwitch.setChecked(enabled);
241            if (mValidListener) {
242                mSwitch.setOnCheckedChangeListener(this);
243            }
244        }
245    }
246
247    /**
248     * Listens to the state change of the location master switch.
249     */
250    @Override
251    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
252        if (isChecked) {
253            setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
254        } else {
255            setLocationMode(Settings.Secure.LOCATION_MODE_OFF);
256        }
257    }
258}
259