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