Home | History | Annotate | Download | only in offscreen
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.cts.verifier.projection.offscreen;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.content.ServiceConnection;
     25 import android.graphics.Color;
     26 import android.graphics.PixelFormat;
     27 import android.media.Image;
     28 import android.media.ImageReader;
     29 import android.media.Ringtone;
     30 import android.media.RingtoneManager;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.IBinder;
     35 import android.os.Looper;
     36 import android.os.RemoteException;
     37 import android.os.SystemClock;
     38 import android.os.Vibrator;
     39 import android.util.DisplayMetrics;
     40 import android.util.Log;
     41 import android.view.KeyEvent;
     42 import android.view.View;
     43 import android.widget.TextView;
     44 
     45 import com.android.cts.verifier.PassFailButtons;
     46 import com.android.cts.verifier.R;
     47 import com.android.cts.verifier.projection.IProjectionService;
     48 import com.android.cts.verifier.projection.ProjectionPresentationType;
     49 import com.android.cts.verifier.projection.ProjectionService;
     50 
     51 import java.nio.ByteBuffer;
     52 
     53 public class ProjectionOffscreenActivity extends PassFailButtons.Activity
     54         implements ImageReader.OnImageAvailableListener {
     55     private static String TAG = ProjectionOffscreenActivity.class.getSimpleName();
     56     private static final int WIDTH = 800;
     57     private static final int HEIGHT = 480;
     58     private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
     59     private static final int TIME_SCREEN_OFF = 5000; // Time screen must remain off for test to run
     60     private static final int DELAYED_RUNNABLE_TIME = 1000; // Time after screen turned off
     61                                                            // keyevent is sent
     62     private static final int RENDERER_DELAY_THRESHOLD = 2000; // Time after keyevent sent that
     63                                                               // rendering must happen by
     64 
     65     protected ImageReader mReader;
     66     protected IProjectionService mService;
     67     protected TextView mStatusView;
     68     protected int mPreviousColor = Color.BLACK;
     69     private long mTimeScreenTurnedOff = 0;
     70     private long mTimeKeyEventSent = 0;
     71     private enum TestStatus { PASSED, FAILED, RUNNING };
     72     protected TestStatus mTestStatus = TestStatus.RUNNING;
     73 
     74     private final Runnable sendKeyEventRunnable = new Runnable() {
     75         @Override
     76         public void run() {
     77             try {
     78                 mService.onKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
     79                 mTimeKeyEventSent = SystemClock.elapsedRealtime();
     80             } catch (RemoteException e) {
     81                 Log.e(TAG, "Error running onKeyEvent", e);
     82             }
     83         }
     84     };
     85 
     86     private final Runnable playNotificationRunnable = new Runnable() {
     87 
     88         @Override
     89         public void run() {
     90             Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
     91             Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
     92             r.play();
     93             ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(1000);
     94         }
     95     };
     96 
     97     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
     98 
     99     @Override
    100     public void onReceive(Context context, Intent intent) {
    101         if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
    102             Handler handler = new Handler(Looper.getMainLooper());
    103             handler.postDelayed(
    104                     sendKeyEventRunnable, DELAYED_RUNNABLE_TIME);
    105             mStatusView.setText("Running test...");
    106             mTimeScreenTurnedOff = SystemClock.elapsedRealtime();
    107             // Notify user its safe to turn screen back on after 5s + fudge factor
    108             handler.postDelayed(playNotificationRunnable, TIME_SCREEN_OFF + 500);
    109         } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
    110             if (SystemClock.elapsedRealtime() - mTimeScreenTurnedOff < TIME_SCREEN_OFF) {
    111                 mStatusView.setText("ERROR: Turned on screen too early");
    112                 getPassButton().setEnabled(false);
    113                 mTestStatus = TestStatus.FAILED;
    114             }
    115         }
    116     }
    117 
    118 };
    119 
    120     protected final ServiceConnection mConnection = new ServiceConnection() {
    121 
    122             @Override
    123         public void onServiceConnected(ComponentName name, IBinder binder) {
    124             mService = IProjectionService.Stub.asInterface(binder);
    125             new Handler().post(new Runnable() {
    126 
    127                     @Override
    128                 public void run() {
    129                     Log.i(TAG, "onServiceConnected thread " + Thread.currentThread());
    130                     try {
    131                         mService.startRendering(mReader.getSurface(), WIDTH, HEIGHT, DENSITY,
    132                                 ProjectionPresentationType.OFFSCREEN.ordinal());
    133                     } catch (RemoteException e) {
    134                         Log.e(TAG, "Failed to execute startRendering", e);
    135                     }
    136 
    137                     IntentFilter filter = new IntentFilter();
    138                     filter.addAction(Intent.ACTION_SCREEN_OFF);
    139                     filter.addAction(Intent.ACTION_SCREEN_ON);
    140 
    141                     registerReceiver(mReceiver, filter);
    142                     mStatusView.setText("Please turn off your screen and turn it back on after " +
    143                             "5 seconds. A sound will be played when it is safe to turn the " +
    144                             "screen back on");
    145                 }
    146 
    147             });
    148 
    149         }
    150 
    151             @Override
    152         public void onServiceDisconnected(ComponentName name) {
    153             mService = null;
    154         }
    155 
    156     };
    157 
    158 
    159 
    160     @Override
    161     protected void onCreate(Bundle savedInstanceState) {
    162         super.onCreate(savedInstanceState);
    163 
    164         View view = getLayoutInflater().inflate(R.layout.poa_main, null);
    165         mStatusView = (TextView) view.findViewById(R.id.poa_status_text);
    166         mStatusView.setText("Waiting for service to bind...");
    167 
    168         setContentView(view);
    169 
    170         setInfoResources(R.string.poa_test, R.string.poa_info, -1);
    171         setPassFailButtonClickListeners();
    172         mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
    173         mReader.setOnImageAvailableListener(this, null);
    174         bindService(new Intent(this, ProjectionService.class), mConnection,
    175                 Context.BIND_AUTO_CREATE);
    176 
    177         getPassButton().setEnabled(false);
    178     }
    179 
    180     @Override
    181     protected void onDestroy() {
    182         super.onDestroy();
    183         unregisterReceiver(mReceiver);
    184         try {
    185             mService.stopRendering();
    186         } catch (RemoteException e) {
    187             Log.e(TAG, "Failed to execute stopRendering", e);
    188         }
    189         if (mConnection != null) {
    190             unbindService(mConnection);
    191         }
    192         mReader.close();
    193     }
    194 
    195     @Override
    196     protected void onPause() {
    197         super.onPause();
    198         if (mTestStatus == TestStatus.FAILED) {
    199             setTestResultAndFinish(false);
    200         }
    201     }
    202 
    203     @Override
    204     public void onImageAvailable(ImageReader reader) {
    205         Log.i(TAG, "onImageAvailable: " + reader);
    206 
    207         if (mTimeKeyEventSent != 0
    208                 && mTestStatus == TestStatus.RUNNING
    209                 && mTimeKeyEventSent + RENDERER_DELAY_THRESHOLD < SystemClock.elapsedRealtime()) {
    210             mTestStatus = TestStatus.FAILED;
    211             mStatusView.setText("Failed: took too long to render");
    212         }
    213 
    214         Image image = reader.acquireLatestImage();
    215 
    216         // No new images available
    217         if (image == null) {
    218             Log.w(TAG, "onImageAvailable called but no image!");
    219             return;
    220         }
    221 
    222         if (mTestStatus == TestStatus.RUNNING) {
    223             int ret = scanImage(image);
    224             if (ret == -1) {
    225                 mStatusView.setText("Failed: saw unexpected color");
    226                 getPassButton().setEnabled(false);
    227                 mTestStatus = TestStatus.FAILED;
    228             } else if (ret != mPreviousColor && ret == Color.BLUE) {
    229                 mStatusView.setText("Success: virtual display rendered expected color");
    230                 getPassButton().setEnabled(true);
    231                 mTestStatus = TestStatus.PASSED;
    232             }
    233         }
    234         image.close();
    235     }
    236 
    237     // modified from the VirtualDisplay Cts test
    238     /**
    239      * Gets the color of the image and ensures all the pixels are the same color
    240      * @param image input image
    241      * @return The color of the image, or -1 for failure
    242      */
    243     private int scanImage(Image image) {
    244         final Image.Plane plane = image.getPlanes()[0];
    245         final ByteBuffer buffer = plane.getBuffer();
    246         final int width = image.getWidth();
    247         final int height = image.getHeight();
    248         final int pixelStride = plane.getPixelStride();
    249         final int rowStride = plane.getRowStride();
    250         final int rowPadding = rowStride - pixelStride * width;
    251 
    252         Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height
    253                 + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride);
    254 
    255         int offset = 0;
    256         int blackPixels = 0;
    257         int bluePixels = 0;
    258         int otherPixels = 0;
    259         for (int y = 0; y < height; y++) {
    260             for (int x = 0; x < width; x++) {
    261                 int pixel = 0;
    262                 pixel |= (buffer.get(offset) & 0xff) << 16;     // R
    263                 pixel |= (buffer.get(offset + 1) & 0xff) << 8;  // G
    264                 pixel |= (buffer.get(offset + 2) & 0xff);       // B
    265                 pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
    266                 if (pixel == Color.BLACK || pixel == 0) {
    267                     blackPixels += 1;
    268                 } else if (pixel == Color.BLUE) {
    269                     bluePixels += 1;
    270                 } else {
    271                     otherPixels += 1;
    272                     if (otherPixels < 10) {
    273                         Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel));
    274                     }
    275                 }
    276                 offset += pixelStride;
    277             }
    278             offset += rowPadding;
    279         }
    280 
    281         // Return a color if it represents all of the pixels.
    282         Log.d(TAG, "- Pixels: " + blackPixels + " black, "
    283                 + bluePixels + " blue, "
    284                 + otherPixels + " other");
    285         if (blackPixels == width * height) {
    286             return Color.BLACK;
    287         } else if (bluePixels == width * height) {
    288             return Color.BLUE;
    289         } else {
    290             return -1;
    291         }
    292     }
    293 }
    294