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