Home | History | Annotate | Download | only in handlers
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.tools.sdkcontroller.handlers;
     18 
     19 import java.nio.ByteBuffer;
     20 import java.util.ArrayList;
     21 import java.util.List;
     22 
     23 import android.content.Context;
     24 import android.hardware.Sensor;
     25 import android.hardware.SensorEvent;
     26 import android.hardware.SensorEventListener;
     27 import android.hardware.SensorManager;
     28 import android.os.Message;
     29 import android.os.SystemClock;
     30 import android.util.Log;
     31 
     32 import com.android.tools.sdkcontroller.lib.Channel;
     33 import com.android.tools.sdkcontroller.lib.ProtocolConstants;
     34 import com.android.tools.sdkcontroller.service.ControllerService;
     35 
     36 /**
     37  * Implements sensors emulation.
     38  */
     39 public class SensorChannel extends Channel {
     40 
     41     @SuppressWarnings("hiding")
     42     private static String TAG = SensorChannel.class.getSimpleName();
     43     @SuppressWarnings("hiding")
     44     private static boolean DEBUG = false;
     45     /**
     46      * The target update time per sensor. Ignored if 0 or negative.
     47      * Sensor updates that arrive faster than this delay are ignored.
     48      * Ideally the emulator can be updated at up to 50 fps, however
     49      * for average power devices something like 20 fps is more
     50      * reasonable.
     51      * Default value should match res/values/strings.xml > sensors_default_sample_rate.
     52      */
     53     private long mUpdateTargetMs = 1000/20; // 20 fps in milliseconds
     54     /** Accumulates average update frequency. */
     55     private long mGlobalAvgUpdateMs = 0;
     56 
     57     /** Array containing monitored sensors. */
     58     private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>();
     59     /** Sensor manager. */
     60     private SensorManager mSenMan;
     61 
     62     /*
     63      * Messages exchanged with the UI.
     64      */
     65 
     66     /**
     67      * Sensor "enabled by emulator" state has changed. Parameter {@code obj} is
     68      * the {@link MonitoredSensor}.
     69      */
     70     public static final int SENSOR_STATE_CHANGED = 1;
     71     /**
     72      * Sensor display value has changed. Parameter {@code obj} is the
     73      * {@link MonitoredSensor}.
     74      */
     75     public static final int SENSOR_DISPLAY_MODIFIED = 2;
     76 
     77     /**
     78      * Constructs SensorChannel instance.
     79      *
     80      * @param service Service context.
     81      */
     82     public SensorChannel(ControllerService service) {
     83         super(service, Channel.SENSOR_CHANNEL);
     84         mSenMan = (SensorManager) service.getSystemService(Context.SENSOR_SERVICE);
     85         // Iterate through the available sensors, adding them to the array.
     86         List<Sensor> sensors = mSenMan.getSensorList(Sensor.TYPE_ALL);
     87         int cur_index = 0;
     88         for (int n = 0; n < sensors.size(); n++) {
     89             Sensor avail_sensor = sensors.get(n);
     90 
     91             // There can be multiple sensors of the same type. We need only one.
     92             if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {
     93                 // The first sensor we've got for the given type is not
     94                 // necessarily the right one. So, use the default sensor
     95                 // for the given type.
     96                 Sensor def_sens = mSenMan.getDefaultSensor(avail_sensor.getType());
     97                 MonitoredSensor to_add = new MonitoredSensor(def_sens);
     98                 cur_index++;
     99                 mSensors.add(to_add);
    100                 if (DEBUG)
    101                     Log.d(TAG, String.format(
    102                             "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
    103                             cur_index, def_sens.getName(), def_sens.getType()));
    104             }
    105         }
    106     }
    107 
    108     /**
    109      * Returns the list of sensors found on the device.
    110      * The list is computed once by {@link #SensorChannel(ControllerService)}.
    111      *
    112      * @return A non-null possibly-empty list of sensors.
    113      */
    114     public List<MonitoredSensor> getSensors() {
    115         return mSensors;
    116     }
    117 
    118     /**
    119      * Set the target update delay throttling per-sensor, in milliseconds.
    120      * <p/>
    121      * For example setting it to 1000/50 means that updates for a <em>given</em> sensor
    122      * faster than 50 fps is discarded.
    123      *
    124      * @param updateTargetMs 0 to disable throttling, otherwise a > 0 millisecond minimum
    125      *   between sensor updates.
    126      */
    127     public void setUpdateTargetMs(long updateTargetMs) {
    128         mUpdateTargetMs = updateTargetMs;
    129     }
    130 
    131     /**
    132      * Returns the actual average time in milliseconds between same-sensor updates.
    133      *
    134      * @return The actual average time in milliseconds between same-sensor updates or 0.
    135      */
    136     public long getActualUpdateMs() {
    137         return mGlobalAvgUpdateMs;
    138     }
    139 
    140     /*
    141      * Channel abstract implementation.
    142      */
    143 
    144     /**
    145      * This method is invoked when this channel is fully connected with its
    146      * counterpart in the emulator.
    147      */
    148     @Override
    149     public void onEmulatorConnected() {
    150         // Emulation is now possible. Note though that it will start only after
    151         // emulator tells us so with SENSORS_START command.
    152         enable();
    153     }
    154 
    155     /**
    156      * This method is invoked when this channel loses connection with its
    157      * counterpart in the emulator.
    158      */
    159     @Override
    160     public void onEmulatorDisconnected() {
    161         // Stop sensor event callbacks.
    162         stopSensors();
    163     }
    164 
    165     /**
    166      * A query has been received from the emulator.
    167      *
    168      * @param query_id Identifies the query. This ID should be used when
    169      *            replying to the query.
    170      * @param query_type Query type.
    171      * @param query_data Query data.
    172      */
    173     @Override
    174     public void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data) {
    175         switch (query_type) {
    176             case ProtocolConstants.SENSORS_QUERY_LIST:
    177                 // Preallocate large response buffer.
    178                 ByteBuffer resp = ByteBuffer.allocate(1024);
    179                 resp.order(getEndian());
    180                 // Iterate through the list of monitored sensors, dumping them
    181                 // into the response buffer.
    182                 for (MonitoredSensor sensor : mSensors) {
    183                     // Entry for each sensor must contain:
    184                     // - an integer for its ID
    185                     // - a zero-terminated emulator-friendly name.
    186                     final byte[] name = sensor.getEmulatorFriendlyName().getBytes();
    187                     final int required_size = 4 + name.length + 1;
    188                     resp = ExpandIf(resp, required_size);
    189                     resp.putInt(sensor.getType());
    190                     resp.put(name);
    191                     resp.put((byte) 0);
    192                 }
    193                 // Terminating entry contains single -1 integer.
    194                 resp = ExpandIf(resp, 4);
    195                 resp.putInt(-1);
    196                 sendQueryResponse(query_id, resp);
    197                 return;
    198 
    199             default:
    200                 Loge("Unknown query " + query_type);
    201                 return;
    202         }
    203     }
    204 
    205     /**
    206      * A message has been received from the emulator.
    207      *
    208      * @param msg_type Message type.
    209      * @param msg_data Packet received from the emulator.
    210      */
    211     @Override
    212     public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {
    213         switch (msg_type) {
    214             case ProtocolConstants.SENSORS_START:
    215                 Log.v(TAG, "Starting sensors emulation.");
    216                 startSensors();
    217                 break;
    218             case ProtocolConstants.SENSORS_STOP:
    219                 Log.v(TAG, "Stopping sensors emulation.");
    220                 stopSensors();
    221                 break;
    222             case ProtocolConstants.SENSORS_ENABLE:
    223                 String enable_name = new String(msg_data.array());
    224                 Log.v(TAG, "Enabling sensor: " + enable_name);
    225                 onEnableSensor(enable_name);
    226                 break;
    227             case ProtocolConstants.SENSORS_DISABLE:
    228                 String disable_name = new String(msg_data.array());
    229                 Log.v(TAG, "Disabling sensor: " + disable_name);
    230                 onDisableSensor(disable_name);
    231                 break;
    232             default:
    233                 Loge("Unknown message type " + msg_type);
    234                 break;
    235         }
    236     }
    237 
    238     /**
    239      * Handles 'enable' message.
    240      *
    241      * @param name Emulator-friendly name of a sensor to enable, or "all" to
    242      *            enable all sensors.
    243      */
    244     private void onEnableSensor(String name) {
    245         if (name.contentEquals("all")) {
    246             // Enable all sensors.
    247             for (MonitoredSensor sensor : mSensors) {
    248                 sensor.enableSensor();
    249             }
    250         } else {
    251             // Lookup sensor by emulator-friendly name.
    252             final MonitoredSensor sensor = getSensorByEFN(name);
    253             if (sensor != null) {
    254                 sensor.enableSensor();
    255             }
    256         }
    257     }
    258 
    259     /**
    260      * Handles 'disable' message.
    261      *
    262      * @param name Emulator-friendly name of a sensor to disable, or "all" to
    263      *            disable all sensors.
    264      */
    265     private void onDisableSensor(String name) {
    266         if (name.contentEquals("all")) {
    267             // Disable all sensors.
    268             for (MonitoredSensor sensor : mSensors) {
    269                 sensor.disableSensor();
    270             }
    271         } else {
    272             // Lookup sensor by emulator-friendly name.
    273             MonitoredSensor sensor = getSensorByEFN(name);
    274             if (sensor != null) {
    275                 sensor.disableSensor();
    276             }
    277         }
    278     }
    279 
    280     /**
    281      * Start listening to all monitored sensors.
    282      */
    283     private void startSensors() {
    284         for (MonitoredSensor sensor : mSensors) {
    285             sensor.startListening();
    286         }
    287     }
    288 
    289     /**
    290      * Stop listening to all monitored sensors.
    291      */
    292     private void stopSensors() {
    293         for (MonitoredSensor sensor : mSensors) {
    294             sensor.stopListening();
    295         }
    296     }
    297 
    298     /***************************************************************************
    299      * Internals
    300      **************************************************************************/
    301 
    302     /**
    303      * Checks if a sensor for the given type is already monitored.
    304      *
    305      * @param type Sensor type (one of the Sensor.TYPE_XXX constants)
    306      * @return true if a sensor for the given type is already monitored, or
    307      *         false if the sensor is not monitored.
    308      */
    309     private boolean isSensorTypeAlreadyMonitored(int type) {
    310         for (MonitoredSensor sensor : mSensors) {
    311             if (sensor.getType() == type) {
    312                 return true;
    313             }
    314         }
    315         return false;
    316     }
    317 
    318     /**
    319      * Looks up a monitored sensor by its emulator-friendly name.
    320      *
    321      * @param name Emulator-friendly name to look up the monitored sensor for.
    322      * @return Monitored sensor for the fiven name, or null if sensor was not
    323      *         found.
    324      */
    325     private MonitoredSensor getSensorByEFN(String name) {
    326         for (MonitoredSensor sensor : mSensors) {
    327             if (sensor.mEmulatorFriendlyName.contentEquals(name)) {
    328                 return sensor;
    329             }
    330         }
    331         return null;
    332     }
    333 
    334     /**
    335      * Encapsulates a sensor that is being monitored. To monitor sensor changes
    336      * each monitored sensor registers with sensor manager as a sensor listener.
    337      * To control sensor monitoring from the UI, each monitored sensor has two
    338      * UI controls associated with it: - A check box (named after sensor) that
    339      * can be used to enable, or disable listening to the sensor changes. - A
    340      * text view where current sensor value is displayed.
    341      */
    342     public class MonitoredSensor {
    343         /** Sensor to monitor. */
    344         private final Sensor mSensor;
    345         /** The sensor name to display in the UI. */
    346         private String mUiName = "";
    347         /** Text view displaying the value of the sensor. */
    348         private String mValue = null;
    349         /** Emulator-friendly name for the sensor. */
    350         private String mEmulatorFriendlyName;
    351         /** Formats string to show in the TextView. */
    352         private String mTextFmt;
    353         /** Sensor values. */
    354         private float[] mValues = new float[3];
    355         /**
    356          * Enabled state. This state is controlled by the emulator, that
    357          * maintains its own list of sensors. So, if a sensor is missing, or is
    358          * disabled in the emulator, it should be disabled in this application.
    359          */
    360         private boolean mEnabledByEmulator = false;
    361         /** User-controlled enabled state. */
    362         private boolean mEnabledByUser = true;
    363         /** Sensor event listener for this sensor. */
    364         private final OurSensorEventListener mListener = new OurSensorEventListener();
    365 
    366         /**
    367          * Constructs MonitoredSensor instance, and register the listeners.
    368          *
    369          * @param sensor Sensor to monitor.
    370          */
    371         MonitoredSensor(Sensor sensor) {
    372             mSensor = sensor;
    373             mEnabledByUser = true;
    374 
    375             // Set appropriate sensor name depending on the type. Unfortunately,
    376             // we can't really use sensor.getName() here, since the value it
    377             // returns (although resembles the purpose) is a bit vaguer than it
    378             // should be. Also choose an appropriate format for the strings that
    379             // display sensor's value.
    380             switch (sensor.getType()) {
    381                 case Sensor.TYPE_ACCELEROMETER:
    382                     mUiName = "Accelerometer";
    383                     mTextFmt = "%+.2f %+.2f %+.2f";
    384                     mEmulatorFriendlyName = "acceleration";
    385                     break;
    386                 case 9: // Sensor.TYPE_GRAVITY is missing in API 7
    387                     mUiName = "Gravity";
    388                     mTextFmt = "%+.2f %+.2f %+.2f";
    389                     mEmulatorFriendlyName = "gravity";
    390                     break;
    391                 case Sensor.TYPE_GYROSCOPE:
    392                     mUiName = "Gyroscope";
    393                     mTextFmt = "%+.2f %+.2f %+.2f";
    394                     mEmulatorFriendlyName = "gyroscope";
    395                     break;
    396                 case Sensor.TYPE_LIGHT:
    397                     mUiName = "Light";
    398                     mTextFmt = "%.0f";
    399                     mEmulatorFriendlyName = "light";
    400                     break;
    401                 case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7
    402                     mUiName = "Linear acceleration";
    403                     mTextFmt = "%+.2f %+.2f %+.2f";
    404                     mEmulatorFriendlyName = "linear-acceleration";
    405                     break;
    406                 case Sensor.TYPE_MAGNETIC_FIELD:
    407                     mUiName = "Magnetic field";
    408                     mTextFmt = "%+.2f %+.2f %+.2f";
    409                     mEmulatorFriendlyName = "magnetic-field";
    410                     break;
    411                 case Sensor.TYPE_ORIENTATION:
    412                     mUiName = "Orientation";
    413                     mTextFmt = "%+03.0f %+03.0f %+03.0f";
    414                     mEmulatorFriendlyName = "orientation";
    415                     break;
    416                 case Sensor.TYPE_PRESSURE:
    417                     mUiName = "Pressure";
    418                     mTextFmt = "%.0f";
    419                     mEmulatorFriendlyName = "pressure";
    420                     break;
    421                 case Sensor.TYPE_PROXIMITY:
    422                     mUiName = "Proximity";
    423                     mTextFmt = "%.0f";
    424                     mEmulatorFriendlyName = "proximity";
    425                     break;
    426                 case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
    427                     mUiName = "Rotation";
    428                     mTextFmt = "%+.2f %+.2f %+.2f";
    429                     mEmulatorFriendlyName = "rotation";
    430                     break;
    431                 case Sensor.TYPE_TEMPERATURE:
    432                     mUiName = "Temperature";
    433                     mTextFmt = "%.0f";
    434                     mEmulatorFriendlyName = "temperature";
    435                     break;
    436                 default:
    437                     mUiName = "<Unknown>";
    438                     mTextFmt = "N/A";
    439                     mEmulatorFriendlyName = "unknown";
    440                     if (DEBUG) Loge("Unknown sensor type " + mSensor.getType() +
    441                             " for sensor " + mSensor.getName());
    442                     break;
    443             }
    444         }
    445 
    446         /**
    447          * Get name for this sensor to display.
    448          *
    449          * @return Name for this sensor to display.
    450          */
    451         public String getUiName() {
    452             return mUiName;
    453         }
    454 
    455         /**
    456          * Gets current sensor value to display.
    457          *
    458          * @return Current sensor value to display.
    459          */
    460         public String getValue() {
    461             if (mValue == null) {
    462                 float[] values = mValues;
    463                 mValue = String.format(mTextFmt, values[0], values[1], values[2]);
    464             }
    465             return mValue == null ? "??" : mValue;
    466         }
    467 
    468         /**
    469          * Checks if monitoring of this this sensor has been enabled by
    470          * emulator.
    471          *
    472          * @return true if monitoring of this this sensor has been enabled by
    473          *         emulator, or false if emulator didn't enable this sensor.
    474          */
    475         public boolean isEnabledByEmulator() {
    476             return mEnabledByEmulator;
    477         }
    478 
    479         /**
    480          * Checks if monitoring of this this sensor has been enabled by user.
    481          *
    482          * @return true if monitoring of this this sensor has been enabled by
    483          *         user, or false if user didn't enable this sensor.
    484          */
    485         public boolean isEnabledByUser() {
    486             return mEnabledByUser;
    487         }
    488 
    489         /**
    490          * Handles checked state change for the associated CheckBox. If check
    491          * box is checked we will register sensor change listener. If it is
    492          * unchecked, we will unregister sensor change listener.
    493          */
    494         public void onCheckedChanged(boolean isChecked) {
    495             mEnabledByUser = isChecked;
    496             if (isChecked) {
    497                 startListening();
    498             } else {
    499                 stopListening();
    500             }
    501         }
    502 
    503         /**
    504          * Gets sensor type.
    505          *
    506          * @return Sensor type as one of the Sensor.TYPE_XXX constants.
    507          */
    508         private int getType() {
    509             return mSensor.getType();
    510         }
    511 
    512         /**
    513          * Gets sensor's emulator-friendly name.
    514          *
    515          * @return Sensor's emulator-friendly name.
    516          */
    517         private String getEmulatorFriendlyName() {
    518             return mEmulatorFriendlyName;
    519         }
    520 
    521         /**
    522          * Starts monitoring the sensor.
    523          * NOTE: This method is called from outside of the UI thread.
    524          */
    525         private void startListening() {
    526             if (mEnabledByEmulator && mEnabledByUser) {
    527                 if (DEBUG) Log.d(TAG, "+++ Sensor " + getEmulatorFriendlyName() + " is started.");
    528                 mSenMan.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_FASTEST);
    529             }
    530         }
    531 
    532         /**
    533          * Stops monitoring the sensor.
    534          * NOTE: This method is called from outside of the UI thread.
    535          */
    536         private void stopListening() {
    537             if (DEBUG) Log.d(TAG, "--- Sensor " + getEmulatorFriendlyName() + " is stopped.");
    538             mSenMan.unregisterListener(mListener);
    539         }
    540 
    541         /**
    542          * Enables sensor events.
    543          * NOTE: This method is called from outside of the UI thread.
    544          */
    545         private void enableSensor() {
    546             if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
    547             mEnabledByEmulator = true;
    548             mValue = null;
    549 
    550             Message msg = Message.obtain();
    551             msg.what = SENSOR_STATE_CHANGED;
    552             msg.obj = MonitoredSensor.this;
    553             notifyUiHandlers(msg);
    554         }
    555 
    556         /**
    557          * Disables sensor events.
    558          * NOTE: This method is called from outside of the UI thread.
    559          */
    560         private void disableSensor() {
    561             if (DEBUG) Log.w(TAG, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
    562             mEnabledByEmulator = false;
    563             mValue = "Disabled by emulator";
    564 
    565             Message msg = Message.obtain();
    566             msg.what = SENSOR_STATE_CHANGED;
    567             msg.obj = MonitoredSensor.this;
    568             notifyUiHandlers(msg);
    569         }
    570 
    571         private class OurSensorEventListener implements SensorEventListener {
    572             /** Last update's time-stamp in local thread millisecond time. */
    573             private long mLastUpdateTS = 0;
    574             /** Last display update time-stamp. */
    575             private long mLastDisplayTS = 0;
    576             /** Preallocated buffer for change notification message. */
    577             private final ByteBuffer mChangeMsg = ByteBuffer.allocate(64);
    578 
    579             /**
    580              * Handles "sensor changed" event.
    581              * This is an implementation of the SensorEventListener interface.
    582              */
    583             @Override
    584             public void onSensorChanged(SensorEvent event) {
    585                 long now = SystemClock.elapsedRealtime();
    586 
    587                 long deltaMs = 0;
    588                 if (mLastUpdateTS != 0) {
    589                     deltaMs = now - mLastUpdateTS;
    590                     if (mUpdateTargetMs > 0 && deltaMs < mUpdateTargetMs) {
    591                         // New sample is arriving too fast. Discard it.
    592                         return;
    593                     }
    594                 }
    595 
    596                 // Format and post message for the emulator.
    597                 float[] values = event.values;
    598                 final int len = values.length;
    599 
    600                 mChangeMsg.order(getEndian());
    601                 mChangeMsg.position(0);
    602                 mChangeMsg.putInt(getType());
    603                 mChangeMsg.putFloat(values[0]);
    604                 if (len > 1) {
    605                     mChangeMsg.putFloat(values[1]);
    606                     if (len > 2) {
    607                         mChangeMsg.putFloat(values[2]);
    608                     }
    609                 }
    610                 postMessage(ProtocolConstants.SENSORS_SENSOR_EVENT, mChangeMsg);
    611 
    612                 // Computes average update time for this sensor and average globally.
    613                 if (mLastUpdateTS != 0) {
    614                     if (mGlobalAvgUpdateMs != 0) {
    615                         mGlobalAvgUpdateMs = (mGlobalAvgUpdateMs + deltaMs) / 2;
    616                     } else {
    617                         mGlobalAvgUpdateMs = deltaMs;
    618                     }
    619                 }
    620                 mLastUpdateTS = now;
    621 
    622                 // Update the UI for the sensor, with a static throttling of 10 fps max.
    623                 if (hasUiHandler()) {
    624                     if (mLastDisplayTS != 0) {
    625                         long uiDeltaMs = now - mLastDisplayTS;
    626                         if (uiDeltaMs < 1000 / 4 /* 4fps in ms */) {
    627                             // Skip this UI update
    628                             return;
    629                         }
    630                     }
    631                     mLastDisplayTS = now;
    632 
    633                     mValues[0] = values[0];
    634                     if (len > 1) {
    635                         mValues[1] = values[1];
    636                         if (len > 2) {
    637                             mValues[2] = values[2];
    638                         }
    639                     }
    640                     mValue = null;
    641 
    642                     Message msg = Message.obtain();
    643                     msg.what = SENSOR_DISPLAY_MODIFIED;
    644                     msg.obj = MonitoredSensor.this;
    645                     notifyUiHandlers(msg);
    646                 }
    647 
    648                 if (DEBUG) {
    649                     long now2 = SystemClock.elapsedRealtime();
    650                     long processingTimeMs = now2 - now;
    651                     Log.d(TAG, String.format("glob %d - local %d > target %d - processing %d -- %s",
    652                             mGlobalAvgUpdateMs, deltaMs, mUpdateTargetMs, processingTimeMs,
    653                             mSensor.getName()));
    654                 }
    655             }
    656 
    657             /**
    658              * Handles "sensor accuracy changed" event.
    659              * This is an implementation of the SensorEventListener interface.
    660              */
    661             @Override
    662             public void onAccuracyChanged(Sensor sensor, int accuracy) {
    663             }
    664         }
    665     } // MonitoredSensor
    666 
    667     /***************************************************************************
    668      * Logging wrappers
    669      **************************************************************************/
    670 
    671     private void Loge(String log) {
    672         mService.addError(log);
    673         Log.e(TAG, log);
    674     }
    675 }
    676