OSDN Git Service

SettingsAppWidgetProvider: do not use setWifiApEnabled
[android-x86/packages-apps-Settings.git] / src / com / android / settings / widget / SettingsAppWidgetProvider.java
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
17 package com.android.settings.widget;
18
19 import android.app.PendingIntent;
20 import android.appwidget.AppWidgetManager;
21 import android.appwidget.AppWidgetProvider;
22 import android.bluetooth.BluetoothAdapter;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.database.ContentObserver;
28 import android.location.LocationManager;
29 import android.net.ConnectivityManager;
30 import android.net.Uri;
31 import android.net.wifi.WifiManager;
32 import android.os.AsyncTask;
33 import android.os.Handler;
34 import android.os.IPowerManager;
35 import android.os.PowerManager;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.os.UserManager;
39 import android.provider.Settings;
40 import android.util.Log;
41 import android.widget.RemoteViews;
42
43 import com.android.settings.R;
44 import com.android.settings.bluetooth.Utils;
45 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
46 import com.android.settingslib.bluetooth.LocalBluetoothManager;
47
48 /**
49  * Provides control of power-related settings from a widget.
50  */
51 public 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                         final ConnectivityManager connectivityManager =
399                                 (ConnectivityManager) context.getSystemService(
400                                         Context.CONNECTIVITY_SERVICE);
401                         connectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
402                     }
403
404                     wifiManager.setWifiEnabled(desiredState);
405                     return null;
406                 }
407             }.execute();
408         }
409
410         @Override
411         public void onActualStateChange(Context context, Intent intent) {
412             if (!WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
413                 return;
414             }
415             int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1);
416             setCurrentState(context, wifiStateToFiveState(wifiState));
417         }
418
419         /**
420          * Converts WifiManager's state values into our
421          * Wifi/Bluetooth-common state values.
422          */
423         private static int wifiStateToFiveState(int wifiState) {
424             switch (wifiState) {
425                 case WifiManager.WIFI_STATE_DISABLED:
426                     return STATE_DISABLED;
427                 case WifiManager.WIFI_STATE_ENABLED:
428                     return STATE_ENABLED;
429                 case WifiManager.WIFI_STATE_DISABLING:
430                     return STATE_TURNING_OFF;
431                 case WifiManager.WIFI_STATE_ENABLING:
432                     return STATE_TURNING_ON;
433                 default:
434                     return STATE_UNKNOWN;
435             }
436         }
437     }
438
439     /**
440      * Subclass of StateTracker to get/set Bluetooth state.
441      */
442     private static final class BluetoothStateTracker extends StateTracker {
443         public int getContainerId() { return R.id.btn_bluetooth; }
444         public int getButtonId() { return R.id.img_bluetooth; }
445         public int getIndicatorId() { return R.id.ind_bluetooth; }
446         public int getButtonDescription() { return R.string.gadget_bluetooth; }
447         public int getButtonImageId(boolean on) {
448             return on ? R.drawable.ic_appwidget_settings_bluetooth_on_holo
449                     : R.drawable.ic_appwidget_settings_bluetooth_off_holo;
450         }
451
452         @Override
453         public int getActualState(Context context) {
454             if (sLocalBluetoothAdapter == null) {
455                 LocalBluetoothManager manager = Utils.getLocalBtManager(context);
456                 if (manager == null) {
457                     return STATE_UNKNOWN;  // On emulator?
458                 }
459                 sLocalBluetoothAdapter = manager.getBluetoothAdapter();
460                 if (sLocalBluetoothAdapter == null) {
461                     return STATE_UNKNOWN;  // On emulator?
462                 }
463             }
464             return bluetoothStateToFiveState(sLocalBluetoothAdapter.getBluetoothState());
465         }
466
467         @Override
468         protected void requestStateChange(Context context, final boolean desiredState) {
469             if (sLocalBluetoothAdapter == null) {
470                 Log.d(TAG, "No LocalBluetoothManager");
471                 return;
472             }
473             // Actually request the Bluetooth change and persistent
474             // settings write off the UI thread, as it can take a
475             // user-noticeable amount of time, especially if there's
476             // disk contention.
477             new AsyncTask<Void, Void, Void>() {
478                 @Override
479                 protected Void doInBackground(Void... args) {
480                     sLocalBluetoothAdapter.setBluetoothEnabled(desiredState);
481                     return null;
482                 }
483             }.execute();
484         }
485
486         @Override
487         public void onActualStateChange(Context context, Intent intent) {
488             if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
489                 return;
490             }
491             int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
492             setCurrentState(context, bluetoothStateToFiveState(bluetoothState));
493         }
494
495         /**
496          * Converts BluetoothAdapter's state values into our
497          * Wifi/Bluetooth-common state values.
498          */
499         private static int bluetoothStateToFiveState(int bluetoothState) {
500             switch (bluetoothState) {
501                 case BluetoothAdapter.STATE_OFF:
502                     return STATE_DISABLED;
503                 case BluetoothAdapter.STATE_ON:
504                     return STATE_ENABLED;
505                 case BluetoothAdapter.STATE_TURNING_ON:
506                     return STATE_TURNING_ON;
507                 case BluetoothAdapter.STATE_TURNING_OFF:
508                     return STATE_TURNING_OFF;
509                 default:
510                     return STATE_UNKNOWN;
511             }
512         }
513     }
514
515     /**
516      * Subclass of StateTracker for location state.
517      */
518     private static final class LocationStateTracker extends StateTracker {
519         private int mCurrentLocationMode = Settings.Secure.LOCATION_MODE_OFF;
520
521         public int getContainerId() { return R.id.btn_location; }
522         public int getButtonId() { return R.id.img_location; }
523         public int getIndicatorId() { return R.id.ind_location; }
524         public int getButtonDescription() { return R.string.gadget_location; }
525         public int getButtonImageId(boolean on) {
526             if (on) {
527                 switch (mCurrentLocationMode) {
528                     case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
529                     case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
530                         return R.drawable.ic_appwidget_settings_location_on_holo;
531                     default:
532                         return R.drawable.ic_appwidget_settings_location_saving_holo;
533                 }
534             }
535
536             return R.drawable.ic_appwidget_settings_location_off_holo;
537         }
538
539         @Override
540         public int getActualState(Context context) {
541             ContentResolver resolver = context.getContentResolver();
542             mCurrentLocationMode = Settings.Secure.getInt(resolver,
543                     Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
544             return (mCurrentLocationMode == Settings.Secure.LOCATION_MODE_OFF)
545                     ? STATE_DISABLED : STATE_ENABLED;
546         }
547
548         @Override
549         public void onActualStateChange(Context context, Intent unused) {
550             // Note: the broadcast location providers changed intent
551             // doesn't include an extras bundles saying what the new value is.
552             setCurrentState(context, getActualState(context));
553         }
554
555         @Override
556         public void requestStateChange(final Context context, final boolean desiredState) {
557             final ContentResolver resolver = context.getContentResolver();
558             new AsyncTask<Void, Void, Boolean>() {
559                 @Override
560                 protected Boolean doInBackground(Void... args) {
561                     final UserManager um =
562                             (UserManager) context.getSystemService(Context.USER_SERVICE);
563                     if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) {
564                         int currentMode = Settings.Secure.getInt(resolver,
565                                 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
566                         int mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
567                         switch (currentMode) {
568                             case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
569                                 mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING;
570                                 break;
571                             case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
572                                 mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
573                                 break;
574                             case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
575                                 mode = Settings.Secure.LOCATION_MODE_OFF;
576                                 break;
577                             case Settings.Secure.LOCATION_MODE_OFF:
578                                 mode = Settings.Secure.LOCATION_MODE_PREVIOUS;
579                                 break;
580                         }
581                         Settings.Secure.putInt(resolver, Settings.Secure.LOCATION_MODE, mode);
582                         return mode != Settings.Secure.LOCATION_MODE_OFF;
583                     }
584
585                     return getActualState(context) == STATE_ENABLED;
586                 }
587
588                 @Override
589                 protected void onPostExecute(Boolean result) {
590                     setCurrentState(
591                         context,
592                         result ? STATE_ENABLED : STATE_DISABLED);
593                     updateWidget(context);
594                 }
595             }.execute();
596         }
597     }
598
599     /**
600      * Subclass of StateTracker for sync state.
601      */
602     private static final class SyncStateTracker extends StateTracker {
603         public int getContainerId() { return R.id.btn_sync; }
604         public int getButtonId() { return R.id.img_sync; }
605         public int getIndicatorId() { return R.id.ind_sync; }
606         public int getButtonDescription() { return R.string.gadget_sync; }
607         public int getButtonImageId(boolean on) {
608             return on ? R.drawable.ic_appwidget_settings_sync_on_holo
609                     : R.drawable.ic_appwidget_settings_sync_off_holo;
610         }
611
612         @Override
613         public int getActualState(Context context) {
614             boolean on = ContentResolver.getMasterSyncAutomatically();
615             return on ? STATE_ENABLED : STATE_DISABLED;
616         }
617
618         @Override
619         public void onActualStateChange(Context context, Intent unused) {
620             setCurrentState(context, getActualState(context));
621         }
622
623         @Override
624         public void requestStateChange(final Context context, final boolean desiredState) {
625             final ConnectivityManager connManager =
626                     (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
627             final boolean sync = ContentResolver.getMasterSyncAutomatically();
628
629             new AsyncTask<Void, Void, Boolean>() {
630                 @Override
631                 protected Boolean doInBackground(Void... args) {
632                     // Turning sync on.
633                     if (desiredState) {
634                         if (!sync) {
635                             ContentResolver.setMasterSyncAutomatically(true);
636                         }
637                         return true;
638                     }
639
640                     // Turning sync off
641                     if (sync) {
642                         ContentResolver.setMasterSyncAutomatically(false);
643                     }
644                     return false;
645                 }
646
647                 @Override
648                 protected void onPostExecute(Boolean result) {
649                     setCurrentState(
650                         context,
651                         result ? STATE_ENABLED : STATE_DISABLED);
652                     updateWidget(context);
653                 }
654             }.execute();
655         }
656     }
657
658     private static void checkObserver(Context context) {
659         if (sSettingsObserver == null) {
660             sSettingsObserver = new SettingsObserver(new Handler(),
661                     context.getApplicationContext());
662             sSettingsObserver.startObserving();
663         }
664     }
665
666     @Override
667     public void onUpdate(Context context, AppWidgetManager appWidgetManager,
668             int[] appWidgetIds) {
669         // Update each requested appWidgetId
670         RemoteViews view = buildUpdate(context);
671
672         for (int i = 0; i < appWidgetIds.length; i++) {
673             appWidgetManager.updateAppWidget(appWidgetIds[i], view);
674         }
675     }
676
677     @Override
678     public void onEnabled(Context context) {
679         checkObserver(context);
680     }
681
682     @Override
683     public void onDisabled(Context context) {
684         if (sSettingsObserver != null) {
685             sSettingsObserver.stopObserving();
686             sSettingsObserver = null;
687         }
688     }
689
690     /**
691      * Load image for given widget and build {@link RemoteViews} for it.
692      */
693     static RemoteViews buildUpdate(Context context) {
694         RemoteViews views = new RemoteViews(context.getPackageName(),
695                 R.layout.widget);
696         views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context,
697                 BUTTON_WIFI));
698         views.setOnClickPendingIntent(R.id.btn_brightness,
699                 getLaunchPendingIntent(context,
700                         BUTTON_BRIGHTNESS));
701         views.setOnClickPendingIntent(R.id.btn_sync,
702                 getLaunchPendingIntent(context,
703                         BUTTON_SYNC));
704         views.setOnClickPendingIntent(R.id.btn_location,
705                 getLaunchPendingIntent(context, BUTTON_LOCATION));
706         views.setOnClickPendingIntent(R.id.btn_bluetooth,
707                 getLaunchPendingIntent(context,
708                         BUTTON_BLUETOOTH));
709
710         updateButtons(views, context);
711         return views;
712     }
713
714     /**
715      * Updates the widget when something changes, or when a button is pushed.
716      *
717      * @param context
718      */
719     public static void updateWidget(Context context) {
720         RemoteViews views = buildUpdate(context);
721         // Update specific list of appWidgetIds if given, otherwise default to all
722         final AppWidgetManager gm = AppWidgetManager.getInstance(context);
723         gm.updateAppWidget(THIS_APPWIDGET, views);
724         checkObserver(context);
725     }
726
727     /**
728      * Updates the buttons based on the underlying states of wifi, etc.
729      *
730      * @param views   The RemoteViews to update.
731      * @param context
732      */
733     private static void updateButtons(RemoteViews views, Context context) {
734         sWifiState.setImageViewResources(context, views);
735         sBluetoothState.setImageViewResources(context, views);
736         sLocationState.setImageViewResources(context, views);
737         sSyncState.setImageViewResources(context, views);
738
739         if (getBrightnessMode(context)) {
740             views.setContentDescription(R.id.btn_brightness,
741                     context.getString(R.string.gadget_brightness_template,
742                             context.getString(R.string.gadget_brightness_state_auto)));
743             views.setImageViewResource(R.id.img_brightness,
744                     R.drawable.ic_appwidget_settings_brightness_auto_holo);
745             views.setImageViewResource(R.id.ind_brightness,
746                     R.drawable.appwidget_settings_ind_on_r_holo);
747         } else {
748             final int brightness = getBrightness(context);
749             final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
750             // Set the icon
751             final int full = (int)(pm.getMaximumScreenBrightnessSetting()
752                     * FULL_BRIGHTNESS_THRESHOLD);
753             final int half = (int)(pm.getMaximumScreenBrightnessSetting()
754                     * HALF_BRIGHTNESS_THRESHOLD);
755             if (brightness > full) {
756                 views.setContentDescription(R.id.btn_brightness,
757                         context.getString(R.string.gadget_brightness_template,
758                                 context.getString(R.string.gadget_brightness_state_full)));
759                 views.setImageViewResource(R.id.img_brightness,
760                         R.drawable.ic_appwidget_settings_brightness_full_holo);
761             } else if (brightness > half) {
762                 views.setContentDescription(R.id.btn_brightness,
763                         context.getString(R.string.gadget_brightness_template,
764                                 context.getString(R.string.gadget_brightness_state_half)));
765                 views.setImageViewResource(R.id.img_brightness,
766                         R.drawable.ic_appwidget_settings_brightness_half_holo);
767             } else {
768                 views.setContentDescription(R.id.btn_brightness,
769                         context.getString(R.string.gadget_brightness_template,
770                                 context.getString(R.string.gadget_brightness_state_off)));
771                 views.setImageViewResource(R.id.img_brightness,
772                         R.drawable.ic_appwidget_settings_brightness_off_holo);
773             }
774             // Set the ON state
775             if (brightness > half) {
776                 views.setImageViewResource(R.id.ind_brightness,
777                         R.drawable.appwidget_settings_ind_on_r_holo);
778             } else {
779                 views.setImageViewResource(R.id.ind_brightness,
780                         R.drawable.appwidget_settings_ind_off_r_holo);
781             }
782         }
783     }
784
785     /**
786      * Creates PendingIntent to notify the widget of a button click.
787      *
788      * @param context
789      * @return
790      */
791     private static PendingIntent getLaunchPendingIntent(Context context,
792             int buttonId) {
793         Intent launchIntent = new Intent();
794         launchIntent.setClass(context, SettingsAppWidgetProvider.class);
795         launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
796         launchIntent.setData(Uri.parse("custom:" + buttonId));
797         PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */,
798                 launchIntent, 0 /* no flags */);
799         return pi;
800     }
801
802     /**
803      * Receives and processes a button pressed intent or state change.
804      *
805      * @param context
806      * @param intent  Indicates the pressed button.
807      */
808     @Override
809     public void onReceive(Context context, Intent intent) {
810         super.onReceive(context, intent);
811         String action = intent.getAction();
812         if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
813             sWifiState.onActualStateChange(context, intent);
814         } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
815             sBluetoothState.onActualStateChange(context, intent);
816         } else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
817             sLocationState.onActualStateChange(context, intent);
818         } else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) {
819             sSyncState.onActualStateChange(context, intent);
820         } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
821             Uri data = intent.getData();
822             int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
823             if (buttonId == BUTTON_WIFI) {
824                 sWifiState.toggleState(context);
825             } else if (buttonId == BUTTON_BRIGHTNESS) {
826                 toggleBrightness(context);
827             } else if (buttonId == BUTTON_SYNC) {
828                 sSyncState.toggleState(context);
829             } else if (buttonId == BUTTON_LOCATION) {
830                 sLocationState.toggleState(context);
831             } else if (buttonId == BUTTON_BLUETOOTH) {
832                 sBluetoothState.toggleState(context);
833             }
834         } else {
835             // Don't fall-through to updating the widget.  The Intent
836             // was something unrelated or that our super class took
837             // care of.
838             return;
839         }
840
841         // State changes fall through
842         updateWidget(context);
843     }
844
845     /**
846      * Gets brightness level.
847      *
848      * @param context
849      * @return brightness level between 0 and 255.
850      */
851     private static int getBrightness(Context context) {
852         try {
853             int brightness = Settings.System.getInt(context.getContentResolver(),
854                     Settings.System.SCREEN_BRIGHTNESS);
855             return brightness;
856         } catch (Exception e) {
857         }
858         return 0;
859     }
860
861     /**
862      * Gets state of brightness mode.
863      *
864      * @param context
865      * @return true if auto brightness is on.
866      */
867     private static boolean getBrightnessMode(Context context) {
868         try {
869             int brightnessMode = Settings.System.getInt(context.getContentResolver(),
870                     Settings.System.SCREEN_BRIGHTNESS_MODE);
871             return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
872         } catch (Exception e) {
873             Log.d(TAG, "getBrightnessMode: " + e);
874         }
875         return false;
876     }
877
878     /**
879      * Increases or decreases the brightness.
880      *
881      * @param context
882      */
883     private void toggleBrightness(Context context) {
884         try {
885             IPowerManager power = IPowerManager.Stub.asInterface(
886                     ServiceManager.getService("power"));
887             if (power != null) {
888                 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
889
890                 ContentResolver cr = context.getContentResolver();
891                 int brightness = Settings.System.getInt(cr,
892                         Settings.System.SCREEN_BRIGHTNESS);
893                 int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
894                 //Only get brightness setting if available
895                 if (context.getResources().getBoolean(
896                         com.android.internal.R.bool.config_automatic_brightness_available)) {
897                     brightnessMode = Settings.System.getInt(cr,
898                             Settings.System.SCREEN_BRIGHTNESS_MODE);
899                 }
900
901                 // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM
902                 // Technically, not a toggle...
903                 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
904                     brightness = pm.getMinimumScreenBrightnessSetting();
905                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
906                 } else if (brightness < pm.getDefaultScreenBrightnessSetting()) {
907                     brightness = pm.getDefaultScreenBrightnessSetting();
908                 } else if (brightness < pm.getMaximumScreenBrightnessSetting()) {
909                     brightness = pm.getMaximumScreenBrightnessSetting();
910                 } else {
911                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
912                     brightness = pm.getMinimumScreenBrightnessSetting();
913                 }
914
915                 if (context.getResources().getBoolean(
916                         com.android.internal.R.bool.config_automatic_brightness_available)) {
917                     // Set screen brightness mode (automatic or manual)
918                     Settings.System.putInt(context.getContentResolver(),
919                             Settings.System.SCREEN_BRIGHTNESS_MODE,
920                             brightnessMode);
921                 } else {
922                     // Make sure we set the brightness if automatic mode isn't available
923                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
924                 }
925                 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
926                     power.setTemporaryScreenBrightnessSettingOverride(brightness);
927                     Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness);
928                 }
929             }
930         } catch (RemoteException e) {
931             Log.d(TAG, "toggleBrightness: " + e);
932         } catch (Settings.SettingNotFoundException e) {
933             Log.d(TAG, "toggleBrightness: " + e);
934         }
935     }
936
937     /** Observer to watch for changes to the BRIGHTNESS setting */
938     private static class SettingsObserver extends ContentObserver {
939
940         private Context mContext;
941
942         SettingsObserver(Handler handler, Context context) {
943             super(handler);
944             mContext = context;
945         }
946
947         void startObserving() {
948             ContentResolver resolver = mContext.getContentResolver();
949             // Listen to brightness and brightness mode
950             resolver.registerContentObserver(Settings.System
951                     .getUriFor(Settings.System.SCREEN_BRIGHTNESS), false, this);
952             resolver.registerContentObserver(Settings.System
953                     .getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false, this);
954             resolver.registerContentObserver(Settings.System
955                     .getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ), false, this);
956         }
957
958         void stopObserving() {
959             mContext.getContentResolver().unregisterContentObserver(this);
960         }
961
962         @Override
963         public void onChange(boolean selfChange) {
964             updateWidget(mContext);
965         }
966     }
967
968 }