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.activities; 18 19 import java.util.HashMap; 20 import java.util.List; 21 import java.util.Map; 22 23 import android.os.Bundle; 24 import android.os.Message; 25 import android.util.Log; 26 import android.view.KeyEvent; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.View.OnFocusChangeListener; 30 import android.view.View.OnKeyListener; 31 import android.widget.CheckBox; 32 import android.widget.CompoundButton; 33 import android.widget.TableLayout; 34 import android.widget.TableRow; 35 import android.widget.TextView; 36 37 import com.android.tools.sdkcontroller.R; 38 import com.android.tools.sdkcontroller.handlers.SensorChannel; 39 import com.android.tools.sdkcontroller.handlers.SensorChannel.MonitoredSensor; 40 import com.android.tools.sdkcontroller.lib.Channel; 41 import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder; 42 import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener; 43 44 /** 45 * Activity that displays and controls the sensors from {@link SensorChannel}. 46 * For each sensor it displays a checkbox that is enabled if the sensor is supported 47 * by the emulator. The user can select whether the sensor is active. It also displays 48 * data from the sensor when available. 49 */ 50 public class SensorActivity extends BaseBindingActivity 51 implements android.os.Handler.Callback { 52 53 @SuppressWarnings("hiding") 54 public static String TAG = SensorActivity.class.getSimpleName(); 55 private static boolean DEBUG = true; 56 57 private static final int MSG_UPDATE_ACTUAL_HZ = 0x31415; 58 59 private TableLayout mTableLayout; 60 private TextView mTextError; 61 private TextView mTextStatus; 62 private TextView mTextTargetHz; 63 private TextView mTextActualHz; 64 private SensorChannel mSensorHandler; 65 66 private final Map<MonitoredSensor, DisplayInfo> mDisplayedSensors = 67 new HashMap<SensorChannel.MonitoredSensor, SensorActivity.DisplayInfo>(); 68 private final android.os.Handler mUiHandler = new android.os.Handler(this); 69 private int mTargetSampleRate; 70 private long mLastActualUpdateMs; 71 72 /** Called when the activity is first created. */ 73 @Override onCreate(Bundle savedInstanceState)74 public void onCreate(Bundle savedInstanceState) { 75 super.onCreate(savedInstanceState); 76 setContentView(R.layout.sensors); 77 mTableLayout = (TableLayout) findViewById(R.id.tableLayout); 78 mTextError = (TextView) findViewById(R.id.textError); 79 mTextStatus = (TextView) findViewById(R.id.textStatus); 80 mTextTargetHz = (TextView) findViewById(R.id.textSampleRate); 81 mTextActualHz = (TextView) findViewById(R.id.textActualRate); 82 updateStatus("Waiting for connection"); 83 84 mTextTargetHz.setOnKeyListener(new OnKeyListener() { 85 @Override 86 public boolean onKey(View v, int keyCode, KeyEvent event) { 87 updateSampleRate(); 88 return false; 89 } 90 }); 91 mTextTargetHz.setOnFocusChangeListener(new OnFocusChangeListener() { 92 @Override 93 public void onFocusChange(View v, boolean hasFocus) { 94 updateSampleRate(); 95 } 96 }); 97 } 98 99 @Override onResume()100 protected void onResume() { 101 if (DEBUG) Log.d(TAG, "onResume"); 102 // BaseBindingActivity.onResume will bind to the service. 103 super.onResume(); 104 updateError(); 105 } 106 107 @Override onPause()108 protected void onPause() { 109 if (DEBUG) Log.d(TAG, "onPause"); 110 // BaseBindingActivity.onResume will unbind from (but not stop) the service. 111 super.onPause(); 112 } 113 114 @Override onDestroy()115 protected void onDestroy() { 116 if (DEBUG) Log.d(TAG, "onDestroy"); 117 super.onDestroy(); 118 removeSensorUi(); 119 } 120 121 // ---------- 122 123 @Override onServiceConnected()124 protected void onServiceConnected() { 125 if (DEBUG) Log.d(TAG, "onServiceConnected"); 126 createSensorUi(); 127 } 128 129 @Override onServiceDisconnected()130 protected void onServiceDisconnected() { 131 if (DEBUG) Log.d(TAG, "onServiceDisconnected"); 132 removeSensorUi(); 133 } 134 135 @Override createControllerListener()136 protected ControllerListener createControllerListener() { 137 return new SensorsControllerListener(); 138 } 139 140 // ---------- 141 142 private class SensorsControllerListener implements ControllerListener { 143 @Override onErrorChanged()144 public void onErrorChanged() { 145 runOnUiThread(new Runnable() { 146 @Override 147 public void run() { 148 updateError(); 149 } 150 }); 151 } 152 153 @Override onStatusChanged()154 public void onStatusChanged() { 155 runOnUiThread(new Runnable() { 156 @Override 157 public void run() { 158 ControllerBinder binder = getServiceBinder(); 159 if (binder != null) { 160 boolean connected = binder.isEmuConnected(); 161 mTableLayout.setEnabled(connected); 162 updateStatus(connected ? "Emulated connected" : "Emulator disconnected"); 163 } 164 } 165 }); 166 } 167 } 168 createSensorUi()169 private void createSensorUi() { 170 final LayoutInflater inflater = getLayoutInflater(); 171 172 if (!mDisplayedSensors.isEmpty()) { 173 removeSensorUi(); 174 } 175 176 mSensorHandler = (SensorChannel) getServiceBinder().getChannel(Channel.SENSOR_CHANNEL); 177 if (mSensorHandler != null) { 178 mSensorHandler.addUiHandler(mUiHandler); 179 mUiHandler.sendEmptyMessage(MSG_UPDATE_ACTUAL_HZ); 180 181 assert mDisplayedSensors.isEmpty(); 182 List<MonitoredSensor> sensors = mSensorHandler.getSensors(); 183 for (MonitoredSensor sensor : sensors) { 184 final TableRow row = (TableRow) inflater.inflate(R.layout.sensor_row, 185 mTableLayout, 186 false); 187 mTableLayout.addView(row); 188 mDisplayedSensors.put(sensor, new DisplayInfo(sensor, row)); 189 } 190 } 191 } 192 removeSensorUi()193 private void removeSensorUi() { 194 if (mSensorHandler != null) { 195 mSensorHandler.removeUiHandler(mUiHandler); 196 mSensorHandler = null; 197 } 198 mTableLayout.removeAllViews(); 199 for (DisplayInfo info : mDisplayedSensors.values()) { 200 info.release(); 201 } 202 mDisplayedSensors.clear(); 203 } 204 205 private class DisplayInfo implements CompoundButton.OnCheckedChangeListener { 206 private MonitoredSensor mSensor; 207 private CheckBox mChk; 208 private TextView mVal; 209 DisplayInfo(MonitoredSensor sensor, TableRow row)210 public DisplayInfo(MonitoredSensor sensor, TableRow row) { 211 mSensor = sensor; 212 213 // Initialize displayed checkbox for this sensor, and register 214 // checked state listener for it. 215 mChk = (CheckBox) row.findViewById(R.id.row_checkbox); 216 mChk.setText(sensor.getUiName()); 217 mChk.setEnabled(sensor.isEnabledByEmulator()); 218 mChk.setChecked(sensor.isEnabledByUser()); 219 mChk.setOnCheckedChangeListener(this); 220 221 // Initialize displayed text box for this sensor. 222 mVal = (TextView) row.findViewById(R.id.row_textview); 223 mVal.setText(sensor.getValue()); 224 } 225 226 /** 227 * Handles checked state change for the associated CheckBox. If check 228 * box is checked we will register sensor change listener. If it is 229 * unchecked, we will unregister sensor change listener. 230 */ 231 @Override onCheckedChanged(CompoundButton buttonView, boolean isChecked)232 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 233 if (mSensor != null) { 234 mSensor.onCheckedChanged(isChecked); 235 } 236 } 237 release()238 public void release() { 239 mChk = null; 240 mVal = null; 241 mSensor = null; 242 243 } 244 updateState()245 public void updateState() { 246 if (mChk != null && mSensor != null) { 247 mChk.setEnabled(mSensor.isEnabledByEmulator()); 248 mChk.setChecked(mSensor.isEnabledByUser()); 249 } 250 } 251 updateValue()252 public void updateValue() { 253 if (mVal != null && mSensor != null) { 254 mVal.setText(mSensor.getValue()); 255 } 256 } 257 } 258 259 /** Implementation of Handler.Callback */ 260 @Override handleMessage(Message msg)261 public boolean handleMessage(Message msg) { 262 DisplayInfo info = null; 263 switch (msg.what) { 264 case SensorChannel.SENSOR_STATE_CHANGED: 265 info = mDisplayedSensors.get(msg.obj); 266 if (info != null) { 267 info.updateState(); 268 } 269 break; 270 case SensorChannel.SENSOR_DISPLAY_MODIFIED: 271 info = mDisplayedSensors.get(msg.obj); 272 if (info != null) { 273 info.updateValue(); 274 } 275 if (mSensorHandler != null) { 276 updateStatus(Integer.toString(mSensorHandler.getMsgSentCount()) + " events sent"); 277 278 // Update the "actual rate" field if the value has changed 279 long ms = mSensorHandler.getActualUpdateMs(); 280 if (ms != mLastActualUpdateMs) { 281 mLastActualUpdateMs = ms; 282 String hz = mLastActualUpdateMs <= 0 ? "--" : 283 Integer.toString((int) Math.ceil(1000. / ms)); 284 mTextActualHz.setText(hz); 285 } 286 } 287 break; 288 case MSG_UPDATE_ACTUAL_HZ: 289 if (mSensorHandler != null) { 290 // Update the "actual rate" field if the value has changed 291 long ms = mSensorHandler.getActualUpdateMs(); 292 if (ms != mLastActualUpdateMs) { 293 mLastActualUpdateMs = ms; 294 String hz = mLastActualUpdateMs <= 0 ? "--" : 295 Integer.toString((int) Math.ceil(1000. / ms)); 296 mTextActualHz.setText(hz); 297 } 298 mUiHandler.sendEmptyMessageDelayed(MSG_UPDATE_ACTUAL_HZ, 1000 /*1s*/); 299 } 300 } 301 return true; // we consumed this message 302 } 303 updateStatus(String status)304 private void updateStatus(String status) { 305 mTextStatus.setVisibility(status == null ? View.GONE : View.VISIBLE); 306 if (status != null) mTextStatus.setText(status); 307 } 308 updateError()309 private void updateError() { 310 ControllerBinder binder = getServiceBinder(); 311 String error = binder == null ? "" : binder.getServiceError(); 312 if (error == null) { 313 error = ""; 314 } 315 316 mTextError.setVisibility(error.length() == 0 ? View.GONE : View.VISIBLE); 317 mTextError.setText(error); 318 } 319 updateSampleRate()320 private void updateSampleRate() { 321 String str = mTextTargetHz.getText().toString(); 322 try { 323 int hz = Integer.parseInt(str.trim()); 324 325 // Cap the value. 50 Hz is a reasonable max value for the emulator. 326 if (hz <= 0 || hz > 50) { 327 hz = 50; 328 } 329 330 if (hz != mTargetSampleRate) { 331 mTargetSampleRate = hz; 332 if (mSensorHandler != null) { 333 mSensorHandler.setUpdateTargetMs(hz <= 0 ? 0 : (int)(1000.0f / hz)); 334 } 335 } 336 } catch (Exception ignore) {} 337 } 338 } 339