2 * Copyright (C) 2012 The Android Open Source Project
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
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, 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
17 package com
.android
.tools
.sdkcontroller
.activities
;
19 import java
.io
.ByteArrayInputStream
;
20 import java
.nio
.ByteBuffer
;
21 import java
.nio
.ByteOrder
;
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
;
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
;
42 * Activity that controls and displays the {@link MultiTouchChannel}.
44 public class MultiTouchActivity
extends BaseBindingActivity
45 implements android
.os
.Handler
.Callback
{
47 @SuppressWarnings("hiding")
48 private static String TAG
= MultiTouchActivity
.class.getSimpleName();
49 private static boolean DEBUG
= true;
51 private volatile MultiTouchChannel mHandler
;
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
;
63 private final TouchListener mTouchListener
= new TouchListener();
64 private final android
.os
.Handler mUiHandler
= new android
.os
.Handler(this);
66 /** Called when the activity is first created. */
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");
76 ApiHelper ah
= ApiHelper
.get();
77 ah
.View_setSystemUiVisibility(mImageView
, View
.SYSTEM_UI_FLAG_LOW_PROFILE
);
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
92 protected void onPause() {
93 if (DEBUG
) Log
.d(TAG
, "onPause");
94 // BaseBindingActivity.onResume will unbind from (but not stop) the service.
96 mImageView
.setEnabled(false);
97 updateStatus("Paused");
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
);
113 protected void onServiceDisconnected() {
114 if (DEBUG
) Log
.d(TAG
, "onServiceDisconnected");
115 if (mHandler
!= null) {
116 mHandler
.removeUiHandler(mUiHandler
);
122 protected ControllerListener
createControllerListener() {
123 return new MultiTouchControllerListener();
128 private class MultiTouchControllerListener
implements ControllerListener
{
130 public void onErrorChanged() {
131 runOnUiThread(new Runnable() {
140 public void onStatusChanged() {
141 runOnUiThread(new Runnable() {
144 ControllerBinder binder
= getServiceBinder();
145 if (binder
!= null) {
146 boolean connected
= binder
.isEmuConnected();
147 mImageView
.setEnabled(connected
);
148 updateStatus(connected ?
"Emulator connected" : "Emulator disconnected");
158 * Implements OnTouchListener interface that receives touch screen events,
159 * and reports them to the emulator application.
161 class TouchListener
implements OnTouchListener
{
163 * Touch screen event handler.
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
;
172 MultiTouchChannel h
= mHandler
;
174 // Build message for the emulator.
175 switch (action_code
) {
176 case MotionEvent
.ACTION_MOVE
:
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
);
184 msg_type
= ProtocolConstants
.MT_MOVE
;
187 case MotionEvent
.ACTION_DOWN
:
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
;
195 case MotionEvent
.ACTION_UP
:
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
;
203 case MotionEvent
.ACTION_POINTER_DOWN
:
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
;
211 case MotionEvent
.ACTION_POINTER_UP
:
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
;
220 Log
.w(TAG
, "Unknown action type: " + action_code
);
224 if (DEBUG
&& bb
!= null) Log
.d(TAG
, bb
.toString());
226 if (h
!= null && bb
!= null) {
227 h
.postMessage(msg_type
, bb
);
233 /** Implementation of Handler.Callback */
235 public boolean handleMessage(Message msg
) {
237 case MultiTouchChannel
.EVENT_MT_START
:
238 MultiTouchChannel h
= mHandler
;
240 mImageView
.setEnabled(true);
241 mImageView
.setOnTouchListener(mTouchListener
);
244 case MultiTouchChannel
.EVENT_MT_STOP
:
245 mImageView
.setOnTouchListener(null);
247 case MultiTouchChannel
.EVENT_FRAME_BUFFER
:
248 onFrameBuffer(((ByteBuffer
) msg
.obj
).array());
249 mHandler
.postMessage(ProtocolConstants
.MT_FB_HANDLED
, (byte[]) null);
252 return true; // we consumed this message
256 * Called when a BLOB query is received from the emulator.
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
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.
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?
269 * @param array contains BLOB data for the query.
271 private void onFrameBuffer(byte[] array
) {
272 final ByteBuffer bb
= ByteBuffer
.wrap(array
);
273 bb
.order(ByteOrder
.LITTLE_ENDIAN
);
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();
287 // Update application display.
288 updateDisplay(disp_width
, disp_height
);
290 if (format
== ProtocolConstants
.MT_FRAME_JPEG
) {
292 * Framebuffer is in JPEG format.
295 final ByteArrayInputStream jpg
= new ByteArrayInputStream(bb
.array());
296 // Advance input stream to JPEG image.
297 jpg
.skip(header_size
);
299 mImageView
.drawJpeg(x
, y
, w
, h
, jpg
);
302 * Framebuffer is in a raw RGB format.
305 final int pixel_num
= h
* w
;
306 // Advance stream to the beginning of framebuffer data.
307 bb
.position(header_size
);
309 // Make sure that mColors is large enough to contain the
311 if (mColors
== null || mColors
.length
< pixel_num
) {
312 mColors
= new int[pixel_num
];
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
);
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
);
334 Log
.w(TAG
, "Invalid framebuffer format: " + format
);
337 mImageView
.drawBitmap(x
, y
, w
, h
, mColors
);
342 * Updates application's screen accordingly to the emulator screen.
344 * @param e_width Width of the emulator screen.
345 * @param e_height Height of the emulator screen.
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
;
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;
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: " +
373 private void updateStatus(String status
) {
374 mTextStatus
.setVisibility(status
== null ? View
.GONE
: View
.VISIBLE
);
375 if (status
!= null) mTextStatus
.setText(status
);
378 private void updateError() {
379 ControllerBinder binder
= getServiceBinder();
380 String error
= binder
== null ?
"" : binder
.getServiceError();
385 mTextError
.setVisibility(error
.length() == 0 ? View
.GONE
: View
.VISIBLE
);
386 mTextError
.setText(error
);