• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ActivityManager;
20 import android.app.PendingIntent;
21 import android.appwidget.AppWidgetManager;
22 import android.appwidget.AppWidgetProvider;
23 import android.bluetooth.BluetoothAdapter;
24 import android.content.ComponentName;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.database.ContentObserver;
29 import android.hardware.display.DisplayManager;
30 import android.location.LocationManager;
31 import android.net.ConnectivityManager;
32 import android.net.Uri;
33 import android.net.wifi.WifiManager;
34 import android.os.AsyncTask;
35 import android.os.Handler;
36 import android.os.IPowerManager;
37 import android.os.PowerManager;
38 import android.os.Process;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.UserManager;
42 import android.provider.Settings;
43 import android.provider.Settings.Secure;
44 import android.util.Log;
45 import android.widget.RemoteViews;
46 
47 import com.android.settings.R;
48 import com.android.settings.bluetooth.Utils;
49 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
50 import com.android.settingslib.bluetooth.LocalBluetoothManager;
51 
52 /**
53  * Provides control of power-related settings from a widget.
54  */
55 public 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          */
toggleState(Context context)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          */
getContainerId()171         public abstract int getContainerId();
172 
173         /**
174          * Return the ID of the main large image button for the setting.
175          */
getButtonId()176         public abstract int getButtonId();
177 
178         /**
179          * Returns the small indicator image ID underneath the setting.
180          */
getIndicatorId()181         public abstract int getIndicatorId();
182 
183         /**
184          * Returns the resource ID of the setting's content description.
185          */
getButtonDescription()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          */
getButtonImageId(boolean on)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          */
getPosition()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          */
setImageViewResources(Context context, RemoteViews views)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          */
getContentDescription(Context context, int stateResId)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          */
onActualStateChange(Context context, Intent intent)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          */
setCurrentState(Context context, int newState)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          */
isTurningOn()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          */
getTriState(Context context)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          */
getActualState(Context context)346         public abstract int getActualState(Context context);
347 
348         /**
349          * Actually make the desired change to the underlying radio
350          * API.
351          */
requestStateChange(Context context, boolean desiredState)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 {
getContainerId()359         public int getContainerId() { return R.id.btn_wifi; }
getButtonId()360         public int getButtonId() { return R.id.img_wifi; }
getIndicatorId()361         public int getIndicatorId() { return R.id.ind_wifi; }
getButtonDescription()362         public int getButtonDescription() { return R.string.gadget_wifi; }
getButtonImageId(boolean on)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
getPosition()369         public int getPosition() { return POS_LEFT; }
370 
371         @Override
getActualState(Context context)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
requestStateChange(Context context, final boolean desiredState)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
onActualStateChange(Context context, Intent intent)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          */
wifiStateToFiveState(int wifiState)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 {
getContainerId()447         public int getContainerId() { return R.id.btn_bluetooth; }
getButtonId()448         public int getButtonId() { return R.id.img_bluetooth; }
getIndicatorId()449         public int getIndicatorId() { return R.id.ind_bluetooth; }
getButtonDescription()450         public int getButtonDescription() { return R.string.gadget_bluetooth; }
getButtonImageId(boolean on)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
getActualState(Context context)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
requestStateChange(Context context, final boolean desiredState)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
onActualStateChange(Context context, Intent intent)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          */
bluetoothStateToFiveState(int bluetoothState)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 
getContainerId()525         public int getContainerId() { return R.id.btn_location; }
getButtonId()526         public int getButtonId() { return R.id.img_location; }
getIndicatorId()527         public int getIndicatorId() { return R.id.ind_location; }
getButtonDescription()528         public int getButtonDescription() { return R.string.gadget_location; }
getButtonImageId(boolean on)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
getActualState(Context context)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
onActualStateChange(Context context, Intent unused)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
requestStateChange(final Context context, final boolean desiredState)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 {
getContainerId()594         public int getContainerId() { return R.id.btn_sync; }
getButtonId()595         public int getButtonId() { return R.id.img_sync; }
getIndicatorId()596         public int getIndicatorId() { return R.id.ind_sync; }
getButtonDescription()597         public int getButtonDescription() { return R.string.gadget_sync; }
getButtonImageId(boolean on)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
getActualState(Context context)604         public int getActualState(Context context) {
605             boolean on = ContentResolver.getMasterSyncAutomatically();
606             return on ? STATE_ENABLED : STATE_DISABLED;
607         }
608 
609         @Override
onActualStateChange(Context context, Intent unused)610         public void onActualStateChange(Context context, Intent unused) {
611             setCurrentState(context, getActualState(context));
612         }
613 
614         @Override
requestStateChange(final Context context, final boolean desiredState)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 
checkObserver(Context context)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
onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)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
onEnabled(Context context)669     public void onEnabled(Context context) {
670         checkObserver(context);
671     }
672 
673     @Override
onDisabled(Context context)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      */
buildUpdate(Context context)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      */
updateWidget(Context context)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      */
updateButtons(RemoteViews views, Context context)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      */
getLaunchPendingIntent(Context context, int buttonId)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
onReceive(Context context, Intent intent)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      */
getBrightness(Context context)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      */
getBrightnessMode(Context context)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      */
toggleBrightness(Context context)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 
SettingsObserver(Handler handler, Context context)928         SettingsObserver(Handler handler, Context context) {
929             super(handler);
930             mContext = context;
931         }
932 
startObserving()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 
stopObserving()944         void stopObserving() {
945             mContext.getContentResolver().unregisterContentObserver(this);
946         }
947 
948         @Override
onChange(boolean selfChange)949         public void onChange(boolean selfChange) {
950             updateWidget(mContext);
951         }
952     }
953 
954 }
955