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