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