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