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.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
     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
     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
     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
    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
    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
    127     protected ControllerListener createControllerListener() {
    128         return new MultiTouchControllerListener();
    129     }
    130 
    131     // ----------
    132 
    133     private class MultiTouchControllerListener implements ControllerListener {
    134         @Override
    135         public void onErrorChanged() {
    136             runOnUiThread(new Runnable() {
    137                 @Override
    138                 public void run() {
    139                     updateError();
    140                 }
    141             });
    142         }
    143 
    144         @Override
    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
    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
    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      */
    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      */
    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 
    353     private void updateStatus(String status) {
    354         mTextStatus.setVisibility(status == null ? View.GONE : View.VISIBLE);
    355         if (status != null) mTextStatus.setText(status);
    356     }
    357 
    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