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