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