Home | History | Annotate | Download | only in activities
      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