2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.widget;
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.IContentService;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.location.LocationManager;
30 import android.net.ConnectivityManager;
31 import android.net.Uri;
32 import android.net.wifi.WifiManager;
33 import android.os.IPowerManager;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.provider.Settings;
37 import android.util.Log;
38 import android.widget.RemoteViews;
39 import android.widget.Toast;
40 import com.android.settings.R;
41 import com.android.settings.bluetooth.LocalBluetoothManager;
44 * Provides control of power-related settings from a widget.
46 public class SettingsAppWidgetProvider extends AppWidgetProvider {
47 static final String TAG = "SettingsAppWidgetProvider";
49 static final ComponentName THIS_APPWIDGET =
50 new ComponentName("com.android.settings",
51 "com.android.settings.widget.SettingsAppWidgetProvider");
53 private static LocalBluetoothManager sLocalBluetoothManager = null;
55 private static final int BUTTON_WIFI = 0;
56 private static final int BUTTON_BRIGHTNESS = 1;
57 private static final int BUTTON_SYNC = 2;
58 private static final int BUTTON_GPS = 3;
59 private static final int BUTTON_BLUETOOTH = 4;
61 // This widget keeps track of two sets of states:
62 // "3-state": STATE_DISABLED, STATE_ENABLED, STATE_INTERMEDIATE
63 // "5-state": STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_UNKNOWN
64 private static final int STATE_DISABLED = 0;
65 private static final int STATE_ENABLED = 1;
66 private static final int STATE_TURNING_ON = 2;
67 private static final int STATE_TURNING_OFF = 3;
68 private static final int STATE_UNKNOWN = 4;
69 private static final int STATE_INTERMEDIATE = 5;
73 * Minimum and maximum brightnesses. Don't go to 0 since that makes the display unusable
75 private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 10;
76 private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON;
77 private static final int DEFAULT_BACKLIGHT = (int) (android.os.Power.BRIGHTNESS_ON * 0.4f);
79 private static final StateTracker sWifiState = new WifiStateTracker();
80 private static final StateTracker sBluetoothState = new BluetoothStateTracker();
83 * The state machine for Wifi and Bluetooth toggling, tracking
84 * reality versus the user's intent.
86 * This is necessary because reality moves relatively slowly
87 * (turning on & off radio drivers), compared to user's
90 private abstract static class StateTracker {
91 // Is the state in the process of changing?
92 private boolean mInTransition = false;
93 private Boolean mActualState = null; // initially not set
94 private Boolean mIntendedState = null; // initially not set
96 // Did a toggle request arrive while a state update was
97 // already in-flight? If so, the mIntendedState needs to be
98 // requested when the other one is done, unless we happened to
99 // arrive at that state already.
100 private boolean mDeferredStateChangeRequestNeeded = false;
103 * User pressed a button to change the state. Something
104 * should immediately appear to the user afterwards, even if
105 * we effectively do nothing. Their press must be heard.
107 public final void toggleState(Context context) {
108 int currentState = getTriState(context);
109 boolean newState = false;
110 switch (currentState) {
117 case STATE_INTERMEDIATE:
118 if (mIntendedState != null) {
119 newState = !mIntendedState;
123 mIntendedState = newState;
125 // We don't send off a transition request if we're
126 // already transitioning. Makes our state tracking
127 // easier, and is probably nicer on lower levels.
128 // (even though they should be able to take it...)
129 mDeferredStateChangeRequestNeeded = true;
131 mInTransition = true;
132 boolean showToast = newState; // only show Toast on the up transition
133 requestStateChange(context, newState, showToast);
138 * Update internal state from a broadcast state change.
140 public abstract void onActualStateChange(Context context, Intent intent);
143 * Sets the value that we're now in. To be called from onActualStateChange.
145 * @param newState one of STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON,
146 * STATE_TURNING_OFF, STATE_UNKNOWN
148 protected final void setCurrentState(Context context, int newState) {
149 boolean wasInTransition = mInTransition;
152 mInTransition = false;
153 mActualState = false;
156 mInTransition = false;
159 case STATE_TURNING_ON:
160 mInTransition = true;
161 mActualState = false;
163 case STATE_TURNING_OFF:
164 mInTransition = true;
169 if (wasInTransition && !mInTransition) {
170 if (mDeferredStateChangeRequestNeeded) {
171 Log.v(TAG, "processing deferred state change");
172 if (mActualState != null && mIntendedState != null &&
173 mIntendedState.equals(mActualState)) {
174 Log.v(TAG, "... but intended state matches, so no changes.");
175 } else if (mIntendedState != null) {
176 mInTransition = true;
177 requestStateChange(context, mIntendedState, false /* no toast */);
179 mDeferredStateChangeRequestNeeded = false;
186 * If we're in a transition mode, this returns true if we're
187 * transitioning towards being enabled.
189 public final boolean isTurningOn() {
190 return mIntendedState != null && mIntendedState;
194 * Returns simplified 3-state value from underlying 5-state.
197 * @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE
199 public final int getTriState(Context context) {
200 switch (getActualState(context)) {
202 return STATE_DISABLED;
204 return STATE_ENABLED;
206 return STATE_INTERMEDIATE;
211 * Gets underlying actual state.
214 * @return STATE_ENABLED, STATE_DISABLED, STATE_ENABLING, STATE_DISABLING,
215 * or or STATE_UNKNOWN.
217 public abstract int getActualState(Context context);
220 * Actually make the desired change to the underlying radio
223 protected abstract void requestStateChange(Context context,
224 boolean desiredState, boolean withToast);
228 * Subclass of StateTracker to get/set Wifi state.
230 private static final class WifiStateTracker extends StateTracker {
232 public int getActualState(Context context) {
233 WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
234 if (wifiManager != null) {
235 return wifiStateToFiveState(wifiManager.getWifiState());
237 return STATE_UNKNOWN;
241 protected void requestStateChange(Context context,
242 boolean desiredState, boolean withToast) {
243 WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
244 if (wifiManager == null) {
245 Log.d(TAG, "No wifiManager.");
249 Toast.makeText(context, R.string.gadget_toggle_wifi, Toast.LENGTH_SHORT).show();
251 wifiManager.setWifiEnabled(desiredState);
255 public void onActualStateChange(Context context, Intent intent) {
256 if (!WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
259 int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1);
260 setCurrentState(context, wifiStateToFiveState(wifiState));
264 * Converts WifiManager's state values into our
265 * Wifi/Bluetooth-common state values.
267 private static int wifiStateToFiveState(int wifiState) {
269 case WifiManager.WIFI_STATE_DISABLED:
270 return STATE_DISABLED;
271 case WifiManager.WIFI_STATE_ENABLED:
272 return STATE_ENABLED;
273 case WifiManager.WIFI_STATE_DISABLING:
274 return STATE_TURNING_OFF;
275 case WifiManager.WIFI_STATE_ENABLING:
276 return STATE_TURNING_ON;
278 return STATE_UNKNOWN;
284 * Subclass of StateTracker to get/set Bluetooth state.
286 private static final class BluetoothStateTracker extends StateTracker {
289 public int getActualState(Context context) {
290 if (sLocalBluetoothManager == null) {
291 sLocalBluetoothManager = LocalBluetoothManager.getInstance(context);
292 if (sLocalBluetoothManager == null) {
293 return STATE_UNKNOWN; // On emulator?
296 return bluetoothStateToFiveState(sLocalBluetoothManager.getBluetoothState());
300 protected void requestStateChange(Context context,
301 boolean desiredState, boolean withToast) {
302 if (sLocalBluetoothManager == null) {
303 Log.d(TAG, "No LocalBluetoothManager");
307 Toast.makeText(context,
308 R.string.gadget_toggle_bluetooth, Toast.LENGTH_SHORT).show();
310 sLocalBluetoothManager.setBluetoothEnabled(desiredState);
314 public void onActualStateChange(Context context, Intent intent) {
315 if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
318 int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
319 setCurrentState(context, bluetoothStateToFiveState(bluetoothState));
323 * Converts BluetoothAdapter's state values into our
324 * Wifi/Bluetooth-common state values.
326 private static int bluetoothStateToFiveState(int bluetoothState) {
327 switch (bluetoothState) {
328 case BluetoothAdapter.STATE_OFF:
329 return STATE_DISABLED;
330 case BluetoothAdapter.STATE_ON:
331 return STATE_ENABLED;
332 case BluetoothAdapter.STATE_TURNING_ON:
333 return STATE_TURNING_ON;
334 case BluetoothAdapter.STATE_TURNING_OFF:
335 return STATE_TURNING_OFF;
337 return STATE_UNKNOWN;
344 public void onUpdate(Context context, AppWidgetManager appWidgetManager,
345 int[] appWidgetIds) {
346 // Update each requested appWidgetId
347 RemoteViews view = buildUpdate(context, -1);
349 for (int i = 0; i < appWidgetIds.length; i++) {
350 appWidgetManager.updateAppWidget(appWidgetIds[i], view);
355 public void onEnabled(Context context) {
356 PackageManager pm = context.getPackageManager();
357 pm.setComponentEnabledSetting(
358 new ComponentName("com.android.settings", ".widget.SettingsAppWidgetProvider"),
359 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
360 PackageManager.DONT_KILL_APP);
364 public void onDisabled(Context context) {
365 Class clazz = com.android.settings.widget.SettingsAppWidgetProvider.class;
366 PackageManager pm = context.getPackageManager();
367 pm.setComponentEnabledSetting(
368 new ComponentName("com.android.settings", ".widget.SettingsAppWidgetProvider"),
369 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
370 PackageManager.DONT_KILL_APP);
374 * Load image for given widget and build {@link RemoteViews} for it.
376 static RemoteViews buildUpdate(Context context, int appWidgetId) {
377 RemoteViews views = new RemoteViews(context.getPackageName(),
379 views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, appWidgetId,
381 views.setOnClickPendingIntent(R.id.btn_brightness,
382 getLaunchPendingIntent(context,
383 appWidgetId, BUTTON_BRIGHTNESS));
384 views.setOnClickPendingIntent(R.id.btn_sync,
385 getLaunchPendingIntent(context,
386 appWidgetId, BUTTON_SYNC));
387 views.setOnClickPendingIntent(R.id.btn_gps,
388 getLaunchPendingIntent(context, appWidgetId, BUTTON_GPS));
389 views.setOnClickPendingIntent(R.id.btn_bluetooth,
390 getLaunchPendingIntent(context,
391 appWidgetId, BUTTON_BLUETOOTH));
393 updateButtons(views, context);
398 * Updates the widget when something changes, or when a button is pushed.
402 public static void updateWidget(Context context) {
403 RemoteViews views = buildUpdate(context, -1);
404 // Update specific list of appWidgetIds if given, otherwise default to all
405 final AppWidgetManager gm = AppWidgetManager.getInstance(context);
406 gm.updateAppWidget(THIS_APPWIDGET, views);
410 * Updates the buttons based on the underlying states of wifi, etc.
412 * @param views The RemoteViews to update.
415 private static void updateButtons(RemoteViews views, Context context) {
416 switch (sWifiState.getTriState(context)) {
418 views.setImageViewResource(R.id.img_wifi,
419 R.drawable.ic_appwidget_settings_wifi_off);
420 views.setImageViewResource(R.id.ind_wifi,
421 R.drawable.appwidget_settings_ind_off_l);
424 views.setImageViewResource(R.id.img_wifi,
425 R.drawable.ic_appwidget_settings_wifi_on);
426 views.setImageViewResource(R.id.ind_wifi,
427 R.drawable.appwidget_settings_ind_on_l);
429 case STATE_INTERMEDIATE:
430 // In the transitional state, the bottom green bar
431 // shows the tri-state (on, off, transitioning), but
432 // the top dark-gray-or-bright-white logo shows the
433 // user's intent. This is much easier to see in
435 if (sWifiState.isTurningOn()) {
436 views.setImageViewResource(R.id.img_wifi,
437 R.drawable.ic_appwidget_settings_wifi_on);
438 views.setImageViewResource(R.id.ind_wifi,
439 R.drawable.appwidget_settings_ind_mid_l);
441 views.setImageViewResource(R.id.img_wifi,
442 R.drawable.ic_appwidget_settings_wifi_off);
443 views.setImageViewResource(R.id.ind_wifi,
444 R.drawable.appwidget_settings_ind_off_l);
448 if (getBrightnessMode(context)) {
449 views.setImageViewResource(R.id.img_brightness,
450 R.drawable.ic_appwidget_settings_brightness_auto);
451 views.setImageViewResource(R.id.ind_brightness,
452 R.drawable.appwidget_settings_ind_on_r);
453 } else if (getBrightness(context)) {
454 views.setImageViewResource(R.id.img_brightness,
455 R.drawable.ic_appwidget_settings_brightness_on);
456 views.setImageViewResource(R.id.ind_brightness,
457 R.drawable.appwidget_settings_ind_on_r);
459 views.setImageViewResource(R.id.img_brightness,
460 R.drawable.ic_appwidget_settings_brightness_off);
461 views.setImageViewResource(R.id.ind_brightness,
462 R.drawable.appwidget_settings_ind_off_r);
464 if (getSync(context)) {
465 views.setImageViewResource(R.id.img_sync, R.drawable.ic_appwidget_settings_sync_on);
466 views.setImageViewResource(R.id.ind_sync, R.drawable.appwidget_settings_ind_on_c);
468 views.setImageViewResource(R.id.img_sync, R.drawable.ic_appwidget_settings_sync_off);
469 views.setImageViewResource(R.id.ind_sync, R.drawable.appwidget_settings_ind_off_c);
471 if (getGpsState(context)) {
472 views.setImageViewResource(R.id.img_gps, R.drawable.ic_appwidget_settings_gps_on);
473 views.setImageViewResource(R.id.ind_gps, R.drawable.appwidget_settings_ind_on_c);
475 views.setImageViewResource(R.id.img_gps, R.drawable.ic_appwidget_settings_gps_off);
476 views.setImageViewResource(R.id.ind_gps, R.drawable.appwidget_settings_ind_off_c);
478 switch (sBluetoothState.getTriState(context)) {
480 views.setImageViewResource(R.id.img_bluetooth,
481 R.drawable.ic_appwidget_settings_bluetooth_off);
482 views.setImageViewResource(R.id.ind_bluetooth,
483 R.drawable.appwidget_settings_ind_off_c);
486 views.setImageViewResource(R.id.img_bluetooth,
487 R.drawable.ic_appwidget_settings_bluetooth_on);
488 views.setImageViewResource(R.id.ind_bluetooth,
489 R.drawable.appwidget_settings_ind_on_c);
491 case STATE_INTERMEDIATE:
492 // In the transitional state, the bottom green bar
493 // shows the tri-state (on, off, transitioning), but
494 // the top dark-gray-or-bright-white logo shows the
495 // user's intent. This is much easier to see in
497 if (sBluetoothState.isTurningOn()) {
498 views.setImageViewResource(R.id.img_bluetooth,
499 R.drawable.ic_appwidget_settings_bluetooth_on);
500 views.setImageViewResource(R.id.ind_bluetooth,
501 R.drawable.appwidget_settings_ind_mid_c);
503 views.setImageViewResource(R.id.img_bluetooth,
504 R.drawable.ic_appwidget_settings_bluetooth_off);
505 views.setImageViewResource(R.id.ind_bluetooth,
506 R.drawable.appwidget_settings_ind_off_c);
513 * Creates PendingIntent to notify the widget of a button click.
519 private static PendingIntent getLaunchPendingIntent(Context context, int appWidgetId,
521 Intent launchIntent = new Intent();
522 launchIntent.setClass(context, SettingsAppWidgetProvider.class);
523 launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
524 launchIntent.setData(Uri.parse("custom:" + buttonId));
525 PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */,
526 launchIntent, 0 /* no flags */);
531 * Receives and processes a button pressed intent or state change.
534 * @param intent Indicates the pressed button.
537 public void onReceive(Context context, Intent intent) {
538 super.onReceive(context, intent);
539 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
540 sWifiState.onActualStateChange(context, intent);
541 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
542 sBluetoothState.onActualStateChange(context, intent);
543 } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
544 Uri data = intent.getData();
545 int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
546 if (buttonId == BUTTON_WIFI) {
547 sWifiState.toggleState(context);
548 } else if (buttonId == BUTTON_BRIGHTNESS) {
549 toggleBrightness(context);
550 } else if (buttonId == BUTTON_SYNC) {
552 } else if (buttonId == BUTTON_GPS) {
554 } else if (buttonId == BUTTON_BLUETOOTH) {
555 sBluetoothState.toggleState(context);
559 // State changes fall through
560 updateWidget(context);
564 * Gets the state of background data.
567 * @return true if enabled
569 private static boolean getBackgroundDataState(Context context) {
570 ConnectivityManager connManager =
571 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
572 return connManager.getBackgroundDataSetting();
576 * Gets the state of auto-sync.
579 * @return true if enabled
581 private static boolean getSync(Context context) {
582 boolean backgroundData = getBackgroundDataState(context);
583 boolean sync = ContentResolver.getMasterSyncAutomatically();
584 return backgroundData && sync;
592 private void toggleSync(Context context) {
593 ConnectivityManager connManager =
594 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
595 boolean backgroundData = getBackgroundDataState(context);
596 boolean sync = ContentResolver.getMasterSyncAutomatically();
598 // four cases to handle:
599 // setting toggled from off to on:
600 // 1. background data was off, sync was off: turn on both
601 if (!backgroundData && !sync) {
602 connManager.setBackgroundDataSetting(true);
603 ContentResolver.setMasterSyncAutomatically(true);
606 // 2. background data was off, sync was on: turn on background data
607 if (!backgroundData && sync) {
608 connManager.setBackgroundDataSetting(true);
611 // 3. background data was on, sync was off: turn on sync
612 if (backgroundData && !sync) {
613 ContentResolver.setMasterSyncAutomatically(true);
616 // setting toggled from on to off:
617 // 4. background data was on, sync was on: turn off sync
618 if (backgroundData && sync) {
619 ContentResolver.setMasterSyncAutomatically(false);
624 * Gets the state of GPS location.
627 * @return true if enabled.
629 private static boolean getGpsState(Context context) {
630 ContentResolver resolver = context.getContentResolver();
631 return Settings.Secure.isLocationProviderEnabled(resolver, LocationManager.GPS_PROVIDER);
635 * Toggles the state of GPS.
639 private void toggleGps(Context context) {
640 ContentResolver resolver = context.getContentResolver();
641 boolean enabled = getGpsState(context);
642 Settings.Secure.setLocationProviderEnabled(resolver, LocationManager.GPS_PROVIDER,
647 * Gets state of brightness.
650 * @return true if more than moderately bright.
652 private static boolean getBrightness(Context context) {
654 IPowerManager power = IPowerManager.Stub.asInterface(
655 ServiceManager.getService("power"));
657 int brightness = Settings.System.getInt(context.getContentResolver(),
658 Settings.System.SCREEN_BRIGHTNESS);
659 return brightness > 100;
661 } catch (Exception e) {
662 Log.d(TAG, "getBrightness: " + e);
668 * Gets state of brightness mode.
671 * @return true if auto brightness is on.
673 private static boolean getBrightnessMode(Context context) {
675 IPowerManager power = IPowerManager.Stub.asInterface(
676 ServiceManager.getService("power"));
678 int brightnessMode = Settings.System.getInt(context.getContentResolver(),
679 Settings.System.SCREEN_BRIGHTNESS_MODE);
680 return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
682 } catch (Exception e) {
683 Log.d(TAG, "getBrightnessMode: " + e);
689 * Increases or decreases the brightness.
693 private void toggleBrightness(Context context) {
695 IPowerManager power = IPowerManager.Stub.asInterface(
696 ServiceManager.getService("power"));
698 ContentResolver cr = context.getContentResolver();
699 int brightness = Settings.System.getInt(cr,
700 Settings.System.SCREEN_BRIGHTNESS);
701 int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
702 //Only get brightness setting if available
703 if (context.getResources().getBoolean(
704 com.android.internal.R.bool.config_automatic_brightness_available)) {
705 brightnessMode = Settings.System.getInt(cr,
706 Settings.System.SCREEN_BRIGHTNESS_MODE);
709 // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM
710 // Technically, not a toggle...
711 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
712 brightness = MINIMUM_BACKLIGHT;
713 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
714 } else if (brightness < DEFAULT_BACKLIGHT) {
715 brightness = DEFAULT_BACKLIGHT;
716 } else if (brightness < MAXIMUM_BACKLIGHT) {
717 brightness = MAXIMUM_BACKLIGHT;
719 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
720 brightness = MINIMUM_BACKLIGHT;
723 if (context.getResources().getBoolean(
724 com.android.internal.R.bool.config_automatic_brightness_available)) {
725 // Set screen brightness mode (automatic or manual)
726 Settings.System.putInt(context.getContentResolver(),
727 Settings.System.SCREEN_BRIGHTNESS_MODE,
730 // Make sure we set the brightness if automatic mode isn't available
731 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
733 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
734 power.setBacklightBrightness(brightness);
735 Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness);
738 } catch (RemoteException e) {
739 Log.d(TAG, "toggleBrightness: " + e);
740 } catch (Settings.SettingNotFoundException e) {
741 Log.d(TAG, "toggleBrightness: " + e);