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