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