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
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()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
onServiceConnected()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
onServiceDisconnected()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
createControllerListener()122     protected ControllerListener createControllerListener() {
123         return new MultiTouchControllerListener();
124     }
125 
126     // ----------
127 
128     private class MultiTouchControllerListener implements ControllerListener {
129         @Override
onErrorChanged()130         public void onErrorChanged() {
131             runOnUiThread(new Runnable() {
132                 @Override
133                 public void run() {
134                     updateError();
135                 }
136             });
137         }
138 
139         @Override
onStatusChanged()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
onTouch(View v, MotionEvent event)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
handleMessage(Message msg)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      */
onFrameBuffer(byte[] array)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      */
updateDisplay(int e_width, int e_height)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 
updateStatus(String status)373     private void updateStatus(String status) {
374         mTextStatus.setVisibility(status == null ? View.GONE : View.VISIBLE);
375         if (status != null) mTextStatus.setText(status);
376     }
377 
updateError()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