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.io.ByteArrayInputStream; 20 import java.nio.ByteBuffer; 21 import java.nio.ByteOrder; 22 23 import android.graphics.Color; 24 import android.os.Bundle; 25 import android.os.Message; 26 import android.util.Log; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.View.OnTouchListener; 30 import android.widget.TextView; 31 32 import com.android.tools.sdkcontroller.R; 33 import com.android.tools.sdkcontroller.handlers.MultiTouchChannel; 34 import com.android.tools.sdkcontroller.lib.Channel; 35 import com.android.tools.sdkcontroller.lib.ProtocolConstants; 36 import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder; 37 import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener; 38 import com.android.tools.sdkcontroller.utils.ApiHelper; 39 import com.android.tools.sdkcontroller.views.MultiTouchView; 40 41 /** 42 * Activity that controls and displays the {@link MultiTouchChannel}. 43 */ 44 public class MultiTouchActivity extends BaseBindingActivity 45 implements android.os.Handler.Callback { 46 47 @SuppressWarnings("hiding") 48 private static String TAG = MultiTouchActivity.class.getSimpleName(); 49 private static boolean DEBUG = true; 50 51 private volatile MultiTouchChannel mHandler; 52 53 private TextView mTextError; 54 private TextView mTextStatus; 55 private MultiTouchView mImageView; 56 /** Width of the emulator's display. */ 57 private int mEmulatorWidth = 0; 58 /** Height of the emulator's display. */ 59 private int mEmulatorHeight = 0; 60 /** Bitmap storage. */ 61 private int[] mColors; 62 63 private final TouchListener mTouchListener = new TouchListener(); 64 private final android.os.Handler mUiHandler = new android.os.Handler(this); 65 66 /** Called when the activity is first created. */ 67 @Override 68 public void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 setContentView(R.layout.multitouch); 71 mImageView = (MultiTouchView) findViewById(R.id.imageView); 72 mTextError = (TextView) findViewById(R.id.textError); 73 mTextStatus = (TextView) findViewById(R.id.textStatus); 74 updateStatus("Waiting for connection"); 75 76 ApiHelper ah = ApiHelper.get(); 77 ah.View_setSystemUiVisibility(mImageView, View.SYSTEM_UI_FLAG_LOW_PROFILE); 78 } 79 80 @Override 81 protected void onResume() { 82 if (DEBUG) Log.d(TAG, "onResume"); 83 // BaseBindingActivity.onResume will bind to the service. 84 // Note: any initialization related to the service or the handler should 85 // go in onServiceConnected() since in this call the service may not be 86 // bound yet. 87 super.onResume(); 88 updateError(); 89 } 90 91 @Override 92 protected void onPause() { 93 if (DEBUG) Log.d(TAG, "onPause"); 94 // BaseBindingActivity.onResume will unbind from (but not stop) the service. 95 super.onPause(); 96 mImageView.setEnabled(false); 97 updateStatus("Paused"); 98 } 99 100 // ---------- 101 102 @Override 103 protected void onServiceConnected() { 104 if (DEBUG) Log.d(TAG, "onServiceConnected"); 105 mHandler = (MultiTouchChannel) getServiceBinder().getChannel(Channel.MULTITOUCH_CHANNEL); 106 if (mHandler != null) { 107 mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight()); 108 mHandler.addUiHandler(mUiHandler); 109 } 110 } 111 112 @Override 113 protected void onServiceDisconnected() { 114 if (DEBUG) Log.d(TAG, "onServiceDisconnected"); 115 if (mHandler != null) { 116 mHandler.removeUiHandler(mUiHandler); 117 mHandler = null; 118 } 119 } 120 121 @Override 122 protected ControllerListener createControllerListener() { 123 return new MultiTouchControllerListener(); 124 } 125 126 // ---------- 127 128 private class MultiTouchControllerListener implements ControllerListener { 129 @Override 130 public void onErrorChanged() { 131 runOnUiThread(new Runnable() { 132 @Override 133 public void run() { 134 updateError(); 135 } 136 }); 137 } 138 139 @Override 140 public void onStatusChanged() { 141 runOnUiThread(new Runnable() { 142 @Override 143 public void run() { 144 ControllerBinder binder = getServiceBinder(); 145 if (binder != null) { 146 boolean connected = binder.isEmuConnected(); 147 mImageView.setEnabled(connected); 148 updateStatus(connected ? "Emulator connected" : "Emulator disconnected"); 149 } 150 } 151 }); 152 } 153 } 154 155 // ---------- 156 157 /** 158 * Implements OnTouchListener interface that receives touch screen events, 159 * and reports them to the emulator application. 160 */ 161 class TouchListener implements OnTouchListener { 162 /** 163 * Touch screen event handler. 164 */ 165 @Override 166 public boolean onTouch(View v, MotionEvent event) { 167 ByteBuffer bb = null; 168 final int action = event.getAction(); 169 final int action_code = action & MotionEvent.ACTION_MASK; 170 final int action_pid_index = action >> MotionEvent.ACTION_POINTER_ID_SHIFT; 171 int msg_type = 0; 172 MultiTouchChannel h = mHandler; 173 174 // Build message for the emulator. 175 switch (action_code) { 176 case MotionEvent.ACTION_MOVE: 177 if (h != null) { 178 bb = ByteBuffer.allocate( 179 event.getPointerCount() * ProtocolConstants.MT_EVENT_ENTRY_SIZE); 180 bb.order(h.getEndian()); 181 for (int n = 0; n < event.getPointerCount(); n++) { 182 mImageView.constructEventMessage(bb, event, n); 183 } 184 msg_type = ProtocolConstants.MT_MOVE; 185 } 186 break; 187 case MotionEvent.ACTION_DOWN: 188 if (h != null) { 189 bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE); 190 bb.order(h.getEndian()); 191 mImageView.constructEventMessage(bb, event, action_pid_index); 192 msg_type = ProtocolConstants.MT_FISRT_DOWN; 193 } 194 break; 195 case MotionEvent.ACTION_UP: 196 if (h != null) { 197 bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE); 198 bb.order(h.getEndian()); 199 bb.putInt(event.getPointerId(action_pid_index)); 200 msg_type = ProtocolConstants.MT_LAST_UP; 201 } 202 break; 203 case MotionEvent.ACTION_POINTER_DOWN: 204 if (h != null) { 205 bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE); 206 bb.order(h.getEndian()); 207 mImageView.constructEventMessage(bb, event, action_pid_index); 208 msg_type = ProtocolConstants.MT_POINTER_DOWN; 209 } 210 break; 211 case MotionEvent.ACTION_POINTER_UP: 212 if (h != null) { 213 bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE); 214 bb.order(h.getEndian()); 215 bb.putInt(event.getPointerId(action_pid_index)); 216 msg_type = ProtocolConstants.MT_POINTER_UP; 217 } 218 break; 219 default: 220 Log.w(TAG, "Unknown action type: " + action_code); 221 return true; 222 } 223 224 if (DEBUG && bb != null) Log.d(TAG, bb.toString()); 225 226 if (h != null && bb != null) { 227 h.postMessage(msg_type, bb); 228 } 229 return true; 230 } 231 } // TouchListener 232 233 /** Implementation of Handler.Callback */ 234 @Override 235 public boolean handleMessage(Message msg) { 236 switch (msg.what) { 237 case MultiTouchChannel.EVENT_MT_START: 238 MultiTouchChannel h = mHandler; 239 if (h != null) { 240 mImageView.setEnabled(true); 241 mImageView.setOnTouchListener(mTouchListener); 242 } 243 break; 244 case MultiTouchChannel.EVENT_MT_STOP: 245 mImageView.setOnTouchListener(null); 246 break; 247 case MultiTouchChannel.EVENT_FRAME_BUFFER: 248 onFrameBuffer(((ByteBuffer) msg.obj).array()); 249 mHandler.postMessage(ProtocolConstants.MT_FB_HANDLED, (byte[]) null); 250 break; 251 } 252 return true; // we consumed this message 253 } 254 255 /** 256 * Called when a BLOB query is received from the emulator. 257 * <p/> 258 * This query is used to deliver framebuffer updates in the emulator. The 259 * blob contains an update header, followed by the bitmap containing updated 260 * rectangle. The header is defined as MTFrameHeader structure in 261 * external/qemu/android/multitouch-port.h 262 * <p/> 263 * NOTE: This method is called from the I/O loop, so all communication with 264 * the emulator will be "on hold" until this method returns. 265 * 266 * TODO ===> CHECK that we can consume that array from a different thread than the producer's. 267 * E.g. does the produce reuse the same array or does it generate a new one each time? 268 * 269 * @param array contains BLOB data for the query. 270 */ 271 private void onFrameBuffer(byte[] array) { 272 final ByteBuffer bb = ByteBuffer.wrap(array); 273 bb.order(ByteOrder.LITTLE_ENDIAN); 274 275 // Read frame header. 276 final int header_size = bb.getInt(); 277 final int disp_width = bb.getInt(); 278 final int disp_height = bb.getInt(); 279 final int x = bb.getInt(); 280 final int y = bb.getInt(); 281 final int w = bb.getInt(); 282 final int h = bb.getInt(); 283 final int bpl = bb.getInt(); 284 final int bpp = bb.getInt(); 285 final int format = bb.getInt(); 286 287 // Update application display. 288 updateDisplay(disp_width, disp_height); 289 290 if (format == ProtocolConstants.MT_FRAME_JPEG) { 291 /* 292 * Framebuffer is in JPEG format. 293 */ 294 295 final ByteArrayInputStream jpg = new ByteArrayInputStream(bb.array()); 296 // Advance input stream to JPEG image. 297 jpg.skip(header_size); 298 // Draw the image. 299 mImageView.drawJpeg(x, y, w, h, jpg); 300 } else { 301 /* 302 * Framebuffer is in a raw RGB format. 303 */ 304 305 final int pixel_num = h * w; 306 // Advance stream to the beginning of framebuffer data. 307 bb.position(header_size); 308 309 // Make sure that mColors is large enough to contain the 310 // update bitmap. 311 if (mColors == null || mColors.length < pixel_num) { 312 mColors = new int[pixel_num]; 313 } 314 315 // Convert the blob bitmap into bitmap that we will display. 316 if (format == ProtocolConstants.MT_FRAME_RGB565) { 317 for (int n = 0; n < pixel_num; n++) { 318 // Blob bitmap is in RGB565 format. 319 final int color = bb.getShort(); 320 final int r = ((color & 0xf800) >> 8) | ((color & 0xf800) >> 14); 321 final int g = ((color & 0x7e0) >> 3) | ((color & 0x7e0) >> 9); 322 final int b = ((color & 0x1f) << 3) | ((color & 0x1f) >> 2); 323 mColors[n] = Color.rgb(r, g, b); 324 } 325 } else if (format == ProtocolConstants.MT_FRAME_RGB888) { 326 for (int n = 0; n < pixel_num; n++) { 327 // Blob bitmap is in RGB565 format. 328 final int r = bb.getChar(); 329 final int g = bb.getChar(); 330 final int b = bb.getChar(); 331 mColors[n] = Color.rgb(r, g, b); 332 } 333 } else { 334 Log.w(TAG, "Invalid framebuffer format: " + format); 335 return; 336 } 337 mImageView.drawBitmap(x, y, w, h, mColors); 338 } 339 } 340 341 /** 342 * Updates application's screen accordingly to the emulator screen. 343 * 344 * @param e_width Width of the emulator screen. 345 * @param e_height Height of the emulator screen. 346 */ 347 private void updateDisplay(int e_width, int e_height) { 348 if (e_width != mEmulatorWidth || e_height != mEmulatorHeight) { 349 mEmulatorWidth = e_width; 350 mEmulatorHeight = e_height; 351 352 boolean rotateDisplay = false; 353 int w = mImageView.getWidth(); 354 int h = mImageView.getHeight(); 355 if (w > h != e_width > e_height) { 356 rotateDisplay = true; 357 int tmp = w; 358 w = h; 359 h = tmp; 360 } 361 362 float dx = (float) w / (float) e_width; 363 float dy = (float) h / (float) e_height; 364 mImageView.setDxDy(dx, dy, rotateDisplay); 365 if (DEBUG) Log.d(TAG, "Dispay updated: " + e_width + " x " + e_height + 366 " -> " + w + " x " + h + " ratio: " + 367 dx + " x " + dy); 368 } 369 } 370 371 // ---------- 372 373 private void updateStatus(String status) { 374 mTextStatus.setVisibility(status == null ? View.GONE : View.VISIBLE); 375 if (status != null) mTextStatus.setText(status); 376 } 377 378 private void updateError() { 379 ControllerBinder binder = getServiceBinder(); 380 String error = binder == null ? "" : binder.getServiceError(); 381 if (error == null) { 382 error = ""; 383 } 384 385 mTextError.setVisibility(error.length() == 0 ? View.GONE : View.VISIBLE); 386 mTextError.setText(error); 387 } 388 } 389