1/*
2 * Copyright (C) 2009 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.widget;
18
19import android.app.PendingIntent;
20import android.appwidget.AppWidgetManager;
21import android.appwidget.AppWidgetProvider;
22import android.bluetooth.BluetoothAdapter;
23import android.content.ComponentName;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.database.ContentObserver;
28import android.location.LocationManager;
29import android.net.ConnectivityManager;
30import android.net.Uri;
31import android.net.wifi.WifiManager;
32import android.os.AsyncTask;
33import android.os.Handler;
34import android.os.IPowerManager;
35import android.os.PowerManager;
36import android.os.RemoteException;
37import android.os.ServiceManager;
38import android.os.UserManager;
39import android.provider.Settings;
40import android.util.Log;
41import android.widget.RemoteViews;
42
43import com.android.settings.R;
44import com.android.settings.bluetooth.LocalBluetoothAdapter;
45import com.android.settings.bluetooth.LocalBluetoothManager;
46
47/**
48 * Provides control of power-related settings from a widget.
49 */
50public class SettingsAppWidgetProvider extends AppWidgetProvider {
51    static final String TAG = "SettingsAppWidgetProvider";
52
53    static final ComponentName THIS_APPWIDGET =
54            new ComponentName("com.android.settings",
55                    "com.android.settings.widget.SettingsAppWidgetProvider");
56
57    private static LocalBluetoothAdapter sLocalBluetoothAdapter = null;
58
59    private static final int BUTTON_WIFI = 0;
60    private static final int BUTTON_BRIGHTNESS = 1;
61    private static final int BUTTON_SYNC = 2;
62    private static final int BUTTON_LOCATION = 3;
63    private static final int BUTTON_BLUETOOTH = 4;
64
65    // This widget keeps track of two sets of states:
66    // "3-state": STATE_DISABLED, STATE_ENABLED, STATE_INTERMEDIATE
67    // "5-state": STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_UNKNOWN
68    private static final int STATE_DISABLED = 0;
69    private static final int STATE_ENABLED = 1;
70    private static final int STATE_TURNING_ON = 2;
71    private static final int STATE_TURNING_OFF = 3;
72    private static final int STATE_UNKNOWN = 4;
73    private static final int STATE_INTERMEDIATE = 5;
74
75    // Position in the widget bar, to enable different graphics for left, center and right buttons
76    private static final int POS_LEFT = 0;
77    private static final int POS_CENTER = 1;
78    private static final int POS_RIGHT = 2;
79
80    private static final int[] IND_DRAWABLE_OFF = {
81        R.drawable.appwidget_settings_ind_off_l_holo,
82        R.drawable.appwidget_settings_ind_off_c_holo,
83        R.drawable.appwidget_settings_ind_off_r_holo
84    };
85
86    private static final int[] IND_DRAWABLE_MID = {
87        R.drawable.appwidget_settings_ind_mid_l_holo,
88        R.drawable.appwidget_settings_ind_mid_c_holo,
89        R.drawable.appwidget_settings_ind_mid_r_holo
90    };
91
92    private static final int[] IND_DRAWABLE_ON = {
93        R.drawable.appwidget_settings_ind_on_l_holo,
94        R.drawable.appwidget_settings_ind_on_c_holo,
95        R.drawable.appwidget_settings_ind_on_r_holo
96    };
97
98    /** Minimum brightness at which the indicator is shown at half-full and ON */
99    private static final float HALF_BRIGHTNESS_THRESHOLD = 0.3f;
100    /** Minimum brightness at which the indicator is shown at full */
101    private static final float FULL_BRIGHTNESS_THRESHOLD = 0.8f;
102
103    private static final StateTracker sWifiState = new WifiStateTracker();
104    private static final StateTracker sBluetoothState = new BluetoothStateTracker();
105    private static final StateTracker sLocationState = new LocationStateTracker();
106    private static final StateTracker sSyncState = new SyncStateTracker();
107    private static SettingsObserver sSettingsObserver;
108
109    /**
110     * The state machine for a setting's toggling, tracking reality
111     * versus the user's intent.
112     *
113     * This is necessary because reality moves relatively slowly
114     * (turning on & off radio drivers), compared to user's
115     * expectations.
116     */
117    private abstract static class StateTracker {
118        // Is the state in the process of changing?
119        private boolean mInTransition = false;
120        private Boolean mActualState = null;  // initially not set
121        private Boolean mIntendedState = null;  // initially not set
122
123        // Did a toggle request arrive while a state update was
124        // already in-flight?  If so, the mIntendedState needs to be
125        // requested when the other one is done, unless we happened to
126        // arrive at that state already.
127        private boolean mDeferredStateChangeRequestNeeded = false;
128
129        /**
130         * User pressed a button to change the state.  Something
131         * should immediately appear to the user afterwards, even if
132         * we effectively do nothing.  Their press must be heard.
133         */
134        public final void toggleState(Context context) {
135            int currentState = getTriState(context);
136            boolean newState = false;
137            switch (currentState) {
138                case STATE_ENABLED:
139                    newState = false;
140                    break;
141                case STATE_DISABLED:
142                    newState = true;
143                    break;
144                case STATE_INTERMEDIATE:
145                    if (mIntendedState != null) {
146                        newState = !mIntendedState;
147                    }
148                    break;
149            }
150            mIntendedState = newState;
151            if (mInTransition) {
152                // We don't send off a transition request if we're
153                // already transitioning.  Makes our state tracking
154                // easier, and is probably nicer on lower levels.
155                // (even though they should be able to take it...)
156                mDeferredStateChangeRequestNeeded = true;
157            } else {
158                mInTransition = true;
159                requestStateChange(context, newState);
160            }
161        }
162
163        /**
164         * Return the ID of the clickable container for the setting.
165         */
166        public abstract int getContainerId();
167
168        /**
169         * Return the ID of the main large image button for the setting.
170         */
171        public abstract int getButtonId();
172
173        /**
174         * Returns the small indicator image ID underneath the setting.
175         */
176        public abstract int getIndicatorId();
177
178        /**
179         * Returns the resource ID of the setting's content description.
180         */
181        public abstract int getButtonDescription();
182
183        /**
184         * Returns the resource ID of the image to show as a function of
185         * the on-vs-off state.
186         */
187        public abstract int getButtonImageId(boolean on);
188
189        /**
190         * Returns the position in the button bar - either POS_LEFT, POS_RIGHT or POS_CENTER.
191         */
192        public int getPosition() { return POS_CENTER; }
193
194        /**
195         * Updates the remote views depending on the state (off, on,
196         * turning off, turning on) of the setting.
197         */
198        public final void setImageViewResources(Context context, RemoteViews views) {
199            int containerId = getContainerId();
200            int buttonId = getButtonId();
201            int indicatorId = getIndicatorId();
202            int pos = getPosition();
203            switch (getTriState(context)) {
204                case STATE_DISABLED:
205                    views.setContentDescription(containerId,
206                        getContentDescription(context, R.string.gadget_state_off));
207                    views.setImageViewResource(buttonId, getButtonImageId(false));
208                    views.setImageViewResource(
209                        indicatorId, IND_DRAWABLE_OFF[pos]);
210                    break;
211                case STATE_ENABLED:
212                    views.setContentDescription(containerId,
213                        getContentDescription(context, R.string.gadget_state_on));
214                    views.setImageViewResource(buttonId, getButtonImageId(true));
215                    views.setImageViewResource(
216                        indicatorId, IND_DRAWABLE_ON[pos]);
217                    break;
218                case STATE_INTERMEDIATE:
219                    // In the transitional state, the bottom green bar
220                    // shows the tri-state (on, off, transitioning), but
221                    // the top dark-gray-or-bright-white logo shows the
222                    // user's intent.  This is much easier to see in
223                    // sunlight.
224                    if (isTurningOn()) {
225                        views.setContentDescription(containerId,
226                            getContentDescription(context, R.string.gadget_state_turning_on));
227                        views.setImageViewResource(buttonId, getButtonImageId(true));
228                        views.setImageViewResource(
229                            indicatorId, IND_DRAWABLE_MID[pos]);
230                    } else {
231                        views.setContentDescription(containerId,
232                            getContentDescription(context, R.string.gadget_state_turning_off));
233                        views.setImageViewResource(buttonId, getButtonImageId(false));
234                        views.setImageViewResource(
235                            indicatorId, IND_DRAWABLE_OFF[pos]);
236                    }
237                    break;
238            }
239        }
240
241        /**
242         * Returns the gadget state template populated with the gadget
243         * description and state.
244         */
245        private final String getContentDescription(Context context, int stateResId) {
246            final String gadget = context.getString(getButtonDescription());
247            final String state = context.getString(stateResId);
248            return context.getString(R.string.gadget_state_template, gadget, state);
249        }
250
251        /**
252         * Update internal state from a broadcast state change.
253         */
254        public abstract void onActualStateChange(Context context, Intent intent);
255
256        /**
257         * Sets the value that we're now in.  To be called from onActualStateChange.
258         *
259         * @param newState one of STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON,
260         *                 STATE_TURNING_OFF, STATE_UNKNOWN
261         */
262        protected final void setCurrentState(Context context, int newState) {
263            final boolean wasInTransition = mInTransition;
264            switch (newState) {
265                case STATE_DISABLED:
266                    mInTransition = false;
267                    mActualState = false;
268                    break;
269                case STATE_ENABLED:
270                    mInTransition = false;
271                    mActualState = true;
272                    break;
273                case STATE_TURNING_ON:
274                    mInTransition = true;
275                    mActualState = false;
276                    break;
277                case STATE_TURNING_OFF:
278                    mInTransition = true;
279                    mActualState = true;
280                    break;
281            }
282
283            if (wasInTransition && !mInTransition) {
284                if (mDeferredStateChangeRequestNeeded) {
285                    Log.v(TAG, "processing deferred state change");
286                    if (mActualState != null && mIntendedState != null &&
287                        mIntendedState.equals(mActualState)) {
288                        Log.v(TAG, "... but intended state matches, so no changes.");
289                    } else if (mIntendedState != null) {
290                        mInTransition = true;
291                        requestStateChange(context, mIntendedState);
292                    }
293                    mDeferredStateChangeRequestNeeded = false;
294                }
295            }
296        }
297
298
299        /**
300         * If we're in a transition mode, this returns true if we're
301         * transitioning towards being enabled.
302         */
303        public final boolean isTurningOn() {
304            return mIntendedState != null && mIntendedState;
305        }
306
307        /**
308         * Returns simplified 3-state value from underlying 5-state.
309         *
310         * @param context
311         * @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE
312         */
313        public final int getTriState(Context context) {
314            if (mInTransition) {
315                // If we know we just got a toggle request recently
316                // (which set mInTransition), don't even ask the
317                // underlying interface for its state.  We know we're
318                // changing.  This avoids blocking the UI thread
319                // during UI refresh post-toggle if the underlying
320                // service state accessor has coarse locking on its
321                // state (to be fixed separately).
322                return STATE_INTERMEDIATE;
323            }
324            switch (getActualState(context)) {
325                case STATE_DISABLED:
326                    return STATE_DISABLED;
327                case STATE_ENABLED:
328                    return STATE_ENABLED;
329                default:
330                    return STATE_INTERMEDIATE;
331            }
332        }
333
334        /**
335         * Gets underlying actual state.
336         *
337         * @param context
338         * @return STATE_ENABLED, STATE_DISABLED, STATE_ENABLING, STATE_DISABLING,
339         *         or or STATE_UNKNOWN.
340         */
341        public abstract int getActualState(Context context);
342
343        /**
344         * Actually make the desired change to the underlying radio
345         * API.
346         */
347        protected abstract void requestStateChange(Context context, boolean desiredState);
348    }
349
350    /**
351     * Subclass of StateTracker to get/set Wifi state.
352     */
353    private static final class WifiStateTracker extends StateTracker {
354        public int getContainerId() { return R.id.btn_wifi; }
355        public int getButtonId() { return R.id.img_wifi; }
356        public int getIndicatorId() { return R.id.ind_wifi; }
357        public int getButtonDescription() { return R.string.gadget_wifi; }
358        public int getButtonImageId(boolean on) {
359            return on ? R.drawable.ic_appwidget_settings_wifi_on_holo
360                    : R.drawable.ic_appwidget_settings_wifi_off_holo;
361        }
362
363        @Override
364        public int getPosition() { return POS_LEFT; }
365
366        @Override
367        public int getActualState(Context context) {
368            WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
369            if (wifiManager != null) {
370                return wifiStateToFiveState(wifiManager.getWifiState());
371            }
372            return STATE_UNKNOWN;
373        }
374
375        @Override
376        protected void requestStateChange(Context context, final boolean desiredState) {
377            final WifiManager wifiManager =
378                    (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
379            if (wifiManager == null) {
380                Log.d(TAG, "No wifiManager.");
381                return;
382            }
383
384            // Actually request the wifi change and persistent
385            // settings write off the UI thread, as it can take a
386            // user-noticeable amount of time, especially if there's
387            // disk contention.
388            new AsyncTask<Void, Void, Void>() {
389                @Override
390                protected Void doInBackground(Void... args) {
391                    /**
392                     * Disable tethering if enabling Wifi
393                     */
394                    int wifiApState = wifiManager.getWifiApState();
395                    if (desiredState && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
396                                         (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
397                        wifiManager.setWifiApEnabled(null, false);
398                    }
399
400                    wifiManager.setWifiEnabled(desiredState);
401                    return null;
402                }
403            }.execute();
404        }
405
406        @Override
407        public void onActualStateChange(Context context, Intent intent) {
408            if (!WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
409                return;
410            }
411            int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1);
412            setCurrentState(context, wifiStateToFiveState(wifiState));
413        }
414
415        /**
416         * Converts WifiManager's state values into our
417         * Wifi/Bluetooth-common state values.
418         */
419        private static int wifiStateToFiveState(int wifiState) {
420            switch (wifiState) {
421                case WifiManager.WIFI_STATE_DISABLED:
422                    return STATE_DISABLED;
423                case WifiManager.WIFI_STATE_ENABLED:
424                    return STATE_ENABLED;
425                case WifiManager.WIFI_STATE_DISABLING:
426                    return STATE_TURNING_OFF;
427                case WifiManager.WIFI_STATE_ENABLING:
428                    return STATE_TURNING_ON;
429                default:
430                    return STATE_UNKNOWN;
431            }
432        }
433    }
434
435    /**
436     * Subclass of StateTracker to get/set Bluetooth state.
437     */
438    private static final class BluetoothStateTracker extends StateTracker {
439        public int getContainerId() { return R.id.btn_bluetooth; }
440        public int getButtonId() { return R.id.img_bluetooth; }
441        public int getIndicatorId() { return R.id.ind_bluetooth; }
442        public int getButtonDescription() { return R.string.gadget_bluetooth; }
443        public int getButtonImageId(boolean on) {
444            return on ? R.drawable.ic_appwidget_settings_bluetooth_on_holo
445                    : R.drawable.ic_appwidget_settings_bluetooth_off_holo;
446        }
447
448        @Override
449        public int getActualState(Context context) {
450            if (sLocalBluetoothAdapter == null) {
451                LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
452                if (manager == null) {
453                    return STATE_UNKNOWN;  // On emulator?
454                }
455                sLocalBluetoothAdapter = manager.getBluetoothAdapter();
456            }
457            return bluetoothStateToFiveState(sLocalBluetoothAdapter.getBluetoothState());
458        }
459
460        @Override
461        protected void requestStateChange(Context context, final boolean desiredState) {
462            if (sLocalBluetoothAdapter == null) {
463                Log.d(TAG, "No LocalBluetoothManager");
464                return;
465            }
466            // Actually request the Bluetooth change and persistent
467            // settings write off the UI thread, as it can take a
468            // user-noticeable amount of time, especially if there's
469            // disk contention.
470            new AsyncTask<Void, Void, Void>() {
471                @Override
472                protected Void doInBackground(Void... args) {
473                    sLocalBluetoothAdapter.setBluetoothEnabled(desiredState);
474                    return null;
475                }
476            }.execute();
477        }
478
479        @Override
480        public void onActualStateChange(Context context, Intent intent) {
481            if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
482                return;
483            }
484            int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
485            setCurrentState(context, bluetoothStateToFiveState(bluetoothState));
486        }
487
488        /**
489         * Converts BluetoothAdapter's state values into our
490         * Wifi/Bluetooth-common state values.
491         */
492        private static int bluetoothStateToFiveState(int bluetoothState) {
493            switch (bluetoothState) {
494                case BluetoothAdapter.STATE_OFF:
495                    return STATE_DISABLED;
496                case BluetoothAdapter.STATE_ON:
497                    return STATE_ENABLED;
498                case BluetoothAdapter.STATE_TURNING_ON:
499                    return STATE_TURNING_ON;
500                case BluetoothAdapter.STATE_TURNING_OFF:
501                    return STATE_TURNING_OFF;
502                default:
503                    return STATE_UNKNOWN;
504            }
505        }
506    }
507
508    /**
509     * Subclass of StateTracker for location state.
510     */
511    private static final class LocationStateTracker extends StateTracker {
512        private int mCurrentLocationMode = Settings.Secure.LOCATION_MODE_OFF;
513
514        public int getContainerId() { return R.id.btn_location; }
515        public int getButtonId() { return R.id.img_location; }
516        public int getIndicatorId() { return R.id.ind_location; }
517        public int getButtonDescription() { return R.string.gadget_location; }
518        public int getButtonImageId(boolean on) {
519            if (on) {
520                switch (mCurrentLocationMode) {
521                    case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
522                    case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
523                        return R.drawable.ic_appwidget_settings_location_on_holo;
524                    default:
525                        return R.drawable.ic_appwidget_settings_location_saving_holo;
526                }
527            }
528
529            return R.drawable.ic_appwidget_settings_location_off_holo;
530        }
531
532        @Override
533        public int getActualState(Context context) {
534            ContentResolver resolver = context.getContentResolver();
535            mCurrentLocationMode = Settings.Secure.getInt(resolver,
536                    Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
537            return (mCurrentLocationMode == Settings.Secure.LOCATION_MODE_OFF)
538                    ? STATE_DISABLED : STATE_ENABLED;
539        }
540
541        @Override
542        public void onActualStateChange(Context context, Intent unused) {
543            // Note: the broadcast location providers changed intent
544            // doesn't include an extras bundles saying what the new value is.
545            setCurrentState(context, getActualState(context));
546        }
547
548        @Override
549        public void requestStateChange(final Context context, final boolean desiredState) {
550            final ContentResolver resolver = context.getContentResolver();
551            new AsyncTask<Void, Void, Boolean>() {
552                @Override
553                protected Boolean doInBackground(Void... args) {
554                    final UserManager um =
555                            (UserManager) context.getSystemService(Context.USER_SERVICE);
556                    if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) {
557                        int currentMode = Settings.Secure.getInt(resolver,
558                                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
559                        int mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
560                        switch (currentMode) {
561                            case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
562                                mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING;
563                                break;
564                            case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
565                                mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
566                                break;
567                            case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
568                                mode = Settings.Secure.LOCATION_MODE_OFF;
569                                break;
570                            case Settings.Secure.LOCATION_MODE_OFF:
571                                mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
572                                break;
573                        }
574                        Settings.Secure.putInt(resolver, Settings.Secure.LOCATION_MODE, mode);
575                        return mode != Settings.Secure.LOCATION_MODE_OFF;
576                    }
577
578                    return getActualState(context) == STATE_ENABLED;
579                }
580
581                @Override
582                protected void onPostExecute(Boolean result) {
583                    setCurrentState(
584                        context,
585                        result ? STATE_ENABLED : STATE_DISABLED);
586                    updateWidget(context);
587                }
588            }.execute();
589        }
590    }
591
592    /**
593     * Subclass of StateTracker for sync state.
594     */
595    private static final class SyncStateTracker extends StateTracker {
596        public int getContainerId() { return R.id.btn_sync; }
597        public int getButtonId() { return R.id.img_sync; }
598        public int getIndicatorId() { return R.id.ind_sync; }
599        public int getButtonDescription() { return R.string.gadget_sync; }
600        public int getButtonImageId(boolean on) {
601            return on ? R.drawable.ic_appwidget_settings_sync_on_holo
602                    : R.drawable.ic_appwidget_settings_sync_off_holo;
603        }
604
605        @Override
606        public int getActualState(Context context) {
607            boolean on = ContentResolver.getMasterSyncAutomatically();
608            return on ? STATE_ENABLED : STATE_DISABLED;
609        }
610
611        @Override
612        public void onActualStateChange(Context context, Intent unused) {
613            setCurrentState(context, getActualState(context));
614        }
615
616        @Override
617        public void requestStateChange(final Context context, final boolean desiredState) {
618            final ConnectivityManager connManager =
619                    (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
620            final boolean sync = ContentResolver.getMasterSyncAutomatically();
621
622            new AsyncTask<Void, Void, Boolean>() {
623                @Override
624                protected Boolean doInBackground(Void... args) {
625                    // Turning sync on.
626                    if (desiredState) {
627                        if (!sync) {
628                            ContentResolver.setMasterSyncAutomatically(true);
629                        }
630                        return true;
631                    }
632
633                    // Turning sync off
634                    if (sync) {
635                        ContentResolver.setMasterSyncAutomatically(false);
636                    }
637                    return false;
638                }
639
640                @Override
641                protected void onPostExecute(Boolean result) {
642                    setCurrentState(
643                        context,
644                        result ? STATE_ENABLED : STATE_DISABLED);
645                    updateWidget(context);
646                }
647            }.execute();
648        }
649    }
650
651    private static void checkObserver(Context context) {
652        if (sSettingsObserver == null) {
653            sSettingsObserver = new SettingsObserver(new Handler(),
654                    context.getApplicationContext());
655            sSettingsObserver.startObserving();
656        }
657    }
658
659    @Override
660    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
661            int[] appWidgetIds) {
662        // Update each requested appWidgetId
663        RemoteViews view = buildUpdate(context);
664
665        for (int i = 0; i < appWidgetIds.length; i++) {
666            appWidgetManager.updateAppWidget(appWidgetIds[i], view);
667        }
668    }
669
670    @Override
671    public void onEnabled(Context context) {
672        checkObserver(context);
673    }
674
675    @Override
676    public void onDisabled(Context context) {
677        if (sSettingsObserver != null) {
678            sSettingsObserver.stopObserving();
679            sSettingsObserver = null;
680        }
681    }
682
683    /**
684     * Load image for given widget and build {@link RemoteViews} for it.
685     */
686    static RemoteViews buildUpdate(Context context) {
687        RemoteViews views = new RemoteViews(context.getPackageName(),
688                R.layout.widget);
689        views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context,
690                BUTTON_WIFI));
691        views.setOnClickPendingIntent(R.id.btn_brightness,
692                getLaunchPendingIntent(context,
693                        BUTTON_BRIGHTNESS));
694        views.setOnClickPendingIntent(R.id.btn_sync,
695                getLaunchPendingIntent(context,
696                        BUTTON_SYNC));
697        views.setOnClickPendingIntent(R.id.btn_location,
698                getLaunchPendingIntent(context, BUTTON_LOCATION));
699        views.setOnClickPendingIntent(R.id.btn_bluetooth,
700                getLaunchPendingIntent(context,
701                        BUTTON_BLUETOOTH));
702
703        updateButtons(views, context);
704        return views;
705    }
706
707    /**
708     * Updates the widget when something changes, or when a button is pushed.
709     *
710     * @param context
711     */
712    public static void updateWidget(Context context) {
713        RemoteViews views = buildUpdate(context);
714        // Update specific list of appWidgetIds if given, otherwise default to all
715        final AppWidgetManager gm = AppWidgetManager.getInstance(context);
716        gm.updateAppWidget(THIS_APPWIDGET, views);
717        checkObserver(context);
718    }
719
720    /**
721     * Updates the buttons based on the underlying states of wifi, etc.
722     *
723     * @param views   The RemoteViews to update.
724     * @param context
725     */
726    private static void updateButtons(RemoteViews views, Context context) {
727        sWifiState.setImageViewResources(context, views);
728        sBluetoothState.setImageViewResources(context, views);
729        sLocationState.setImageViewResources(context, views);
730        sSyncState.setImageViewResources(context, views);
731
732        if (getBrightnessMode(context)) {
733            views.setContentDescription(R.id.btn_brightness,
734                    context.getString(R.string.gadget_brightness_template,
735                            context.getString(R.string.gadget_brightness_state_auto)));
736            views.setImageViewResource(R.id.img_brightness,
737                    R.drawable.ic_appwidget_settings_brightness_auto_holo);
738            views.setImageViewResource(R.id.ind_brightness,
739                    R.drawable.appwidget_settings_ind_on_r_holo);
740        } else {
741            final int brightness = getBrightness(context);
742            final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
743            // Set the icon
744            final int full = (int)(pm.getMaximumScreenBrightnessSetting()
745                    * FULL_BRIGHTNESS_THRESHOLD);
746            final int half = (int)(pm.getMaximumScreenBrightnessSetting()
747                    * HALF_BRIGHTNESS_THRESHOLD);
748            if (brightness > full) {
749                views.setContentDescription(R.id.btn_brightness,
750                        context.getString(R.string.gadget_brightness_template,
751                                context.getString(R.string.gadget_brightness_state_full)));
752                views.setImageViewResource(R.id.img_brightness,
753                        R.drawable.ic_appwidget_settings_brightness_full_holo);
754            } else if (brightness > half) {
755                views.setContentDescription(R.id.btn_brightness,
756                        context.getString(R.string.gadget_brightness_template,
757                                context.getString(R.string.gadget_brightness_state_half)));
758                views.setImageViewResource(R.id.img_brightness,
759                        R.drawable.ic_appwidget_settings_brightness_half_holo);
760            } else {
761                views.setContentDescription(R.id.btn_brightness,
762                        context.getString(R.string.gadget_brightness_template,
763                                context.getString(R.string.gadget_brightness_state_off)));
764                views.setImageViewResource(R.id.img_brightness,
765                        R.drawable.ic_appwidget_settings_brightness_off_holo);
766            }
767            // Set the ON state
768            if (brightness > half) {
769                views.setImageViewResource(R.id.ind_brightness,
770                        R.drawable.appwidget_settings_ind_on_r_holo);
771            } else {
772                views.setImageViewResource(R.id.ind_brightness,
773                        R.drawable.appwidget_settings_ind_off_r_holo);
774            }
775        }
776    }
777
778    /**
779     * Creates PendingIntent to notify the widget of a button click.
780     *
781     * @param context
782     * @return
783     */
784    private static PendingIntent getLaunchPendingIntent(Context context,
785            int buttonId) {
786        Intent launchIntent = new Intent();
787        launchIntent.setClass(context, SettingsAppWidgetProvider.class);
788        launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
789        launchIntent.setData(Uri.parse("custom:" + buttonId));
790        PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */,
791                launchIntent, 0 /* no flags */);
792        return pi;
793    }
794
795    /**
796     * Receives and processes a button pressed intent or state change.
797     *
798     * @param context
799     * @param intent  Indicates the pressed button.
800     */
801    @Override
802    public void onReceive(Context context, Intent intent) {
803        super.onReceive(context, intent);
804        String action = intent.getAction();
805        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
806            sWifiState.onActualStateChange(context, intent);
807        } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
808            sBluetoothState.onActualStateChange(context, intent);
809        } else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
810            sLocationState.onActualStateChange(context, intent);
811        } else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) {
812            sSyncState.onActualStateChange(context, intent);
813        } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
814            Uri data = intent.getData();
815            int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
816            if (buttonId == BUTTON_WIFI) {
817                sWifiState.toggleState(context);
818            } else if (buttonId == BUTTON_BRIGHTNESS) {
819                toggleBrightness(context);
820            } else if (buttonId == BUTTON_SYNC) {
821                sSyncState.toggleState(context);
822            } else if (buttonId == BUTTON_LOCATION) {
823                sLocationState.toggleState(context);
824            } else if (buttonId == BUTTON_BLUETOOTH) {
825                sBluetoothState.toggleState(context);
826            }
827        } else {
828            // Don't fall-through to updating the widget.  The Intent
829            // was something unrelated or that our super class took
830            // care of.
831            return;
832        }
833
834        // State changes fall through
835        updateWidget(context);
836    }
837
838    /**
839     * Gets brightness level.
840     *
841     * @param context
842     * @return brightness level between 0 and 255.
843     */
844    private static int getBrightness(Context context) {
845        try {
846            int brightness = Settings.System.getInt(context.getContentResolver(),
847                    Settings.System.SCREEN_BRIGHTNESS);
848            return brightness;
849        } catch (Exception e) {
850        }
851        return 0;
852    }
853
854    /**
855     * Gets state of brightness mode.
856     *
857     * @param context
858     * @return true if auto brightness is on.
859     */
860    private static boolean getBrightnessMode(Context context) {
861        try {
862            int brightnessMode = Settings.System.getInt(context.getContentResolver(),
863                    Settings.System.SCREEN_BRIGHTNESS_MODE);
864            return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
865        } catch (Exception e) {
866            Log.d(TAG, "getBrightnessMode: " + e);
867        }
868        return false;
869    }
870
871    /**
872     * Increases or decreases the brightness.
873     *
874     * @param context
875     */
876    private void toggleBrightness(Context context) {
877        try {
878            IPowerManager power = IPowerManager.Stub.asInterface(
879                    ServiceManager.getService("power"));
880            if (power != null) {
881                PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
882
883                ContentResolver cr = context.getContentResolver();
884                int brightness = Settings.System.getInt(cr,
885                        Settings.System.SCREEN_BRIGHTNESS);
886                int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
887                //Only get brightness setting if available
888                if (context.getResources().getBoolean(
889                        com.android.internal.R.bool.config_automatic_brightness_available)) {
890                    brightnessMode = Settings.System.getInt(cr,
891                            Settings.System.SCREEN_BRIGHTNESS_MODE);
892                }
893
894                // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM
895                // Technically, not a toggle...
896                if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
897                    brightness = pm.getMinimumScreenBrightnessSetting();
898                    brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
899                } else if (brightness < pm.getDefaultScreenBrightnessSetting()) {
900                    brightness = pm.getDefaultScreenBrightnessSetting();
901                } else if (brightness < pm.getMaximumScreenBrightnessSetting()) {
902                    brightness = pm.getMaximumScreenBrightnessSetting();
903                } else {
904                    brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
905                    brightness = pm.getMinimumScreenBrightnessSetting();
906                }
907
908                if (context.getResources().getBoolean(
909                        com.android.internal.R.bool.config_automatic_brightness_available)) {
910                    // Set screen brightness mode (automatic or manual)
911                    Settings.System.putInt(context.getContentResolver(),
912                            Settings.System.SCREEN_BRIGHTNESS_MODE,
913                            brightnessMode);
914                } else {
915                    // Make sure we set the brightness if automatic mode isn't available
916                    brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
917                }
918                if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
919                    power.setTemporaryScreenBrightnessSettingOverride(brightness);
920                    Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness);
921                }
922            }
923        } catch (RemoteException e) {
924            Log.d(TAG, "toggleBrightness: " + e);
925        } catch (Settings.SettingNotFoundException e) {
926            Log.d(TAG, "toggleBrightness: " + e);
927        }
928    }
929
930    /** Observer to watch for changes to the BRIGHTNESS setting */
931    private static class SettingsObserver extends ContentObserver {
932
933        private Context mContext;
934
935        SettingsObserver(Handler handler, Context context) {
936            super(handler);
937            mContext = context;
938        }
939
940        void startObserving() {
941            ContentResolver resolver = mContext.getContentResolver();
942            // Listen to brightness and brightness mode
943            resolver.registerContentObserver(Settings.System
944                    .getUriFor(Settings.System.SCREEN_BRIGHTNESS), false, this);
945            resolver.registerContentObserver(Settings.System
946                    .getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false, this);
947        }
948
949        void stopObserving() {
950            mContext.getContentResolver().unregisterContentObserver(this);
951        }
952
953        @Override
954        public void onChange(boolean selfChange) {
955            updateWidget(mContext);
956        }
957    }
958
959}
960