blob: c202bb1aaeeacb80d182a57e1b6e48a0668b94c3 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.verifier.projection.offscreen;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.media.Image;
import android.media.ImageReader;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
import com.android.cts.verifier.projection.IProjectionService;
import com.android.cts.verifier.projection.ProjectionPresentationType;
import com.android.cts.verifier.projection.ProjectionService;
import java.nio.ByteBuffer;
public class ProjectionOffscreenActivity extends PassFailButtons.Activity
implements ImageReader.OnImageAvailableListener {
private static String TAG = ProjectionOffscreenActivity.class.getSimpleName();
private static final int WIDTH = 800;
private static final int HEIGHT = 480;
private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
private static final int TIME_SCREEN_OFF = 5000; // Time screen must remain off for test to run
private static final int DELAYED_RUNNABLE_TIME = 1000; // Time after screen turned off
// keyevent is sent
private static final int RENDERER_DELAY_THRESHOLD = 2000; // Time after keyevent sent that
// rendering must happen by
protected ImageReader mReader;
protected IProjectionService mService;
protected TextView mStatusView;
protected int mPreviousColor = Color.BLACK;
private long mTimeScreenTurnedOff = 0;
private long mTimeKeyEventSent = 0;
private enum TestStatus { PASSED, FAILED, RUNNING };
protected TestStatus mTestStatus = TestStatus.RUNNING;
private final Runnable sendKeyEventRunnable = new Runnable() {
@Override
public void run() {
try {
mService.onKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
mTimeKeyEventSent = SystemClock.uptimeMillis();
} catch (RemoteException e) {
Log.e(TAG, "Error running onKeyEvent", e);
}
}
};
private final Runnable playNotificationRunnable = new Runnable() {
@Override
public void run() {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(1000);
}
};
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(
sendKeyEventRunnable, DELAYED_RUNNABLE_TIME);
mStatusView.setText("Running test...");
mTimeScreenTurnedOff = SystemClock.uptimeMillis();
// Notify user its safe to turn screen back on after 5s + fudge factor
handler.postDelayed(playNotificationRunnable, TIME_SCREEN_OFF + 500);
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
if (SystemClock.uptimeMillis() - mTimeScreenTurnedOff < TIME_SCREEN_OFF) {
mStatusView.setText("ERROR: Turned on screen too early");
getPassButton().setEnabled(false);
mTestStatus = TestStatus.FAILED;
}
}
}
};
protected final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
mService = IProjectionService.Stub.asInterface(binder);
new Handler().post(new Runnable() {
@Override
public void run() {
Log.i(TAG, "onServiceConnected thread " + Thread.currentThread());
try {
mService.startRendering(mReader.getSurface(), WIDTH, HEIGHT, DENSITY,
ProjectionPresentationType.OFFSCREEN.ordinal());
} catch (RemoteException e) {
Log.e(TAG, "Failed to execute startRendering", e);
}
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(mReceiver, filter);
mStatusView.setText("Please turn off your screen and turn it back on after " +
"5 seconds. A sound will be played when it is safe to turn the " +
"screen back on");
}
});
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = getLayoutInflater().inflate(R.layout.poa_main, null);
mStatusView = (TextView) view.findViewById(R.id.poa_status_text);
mStatusView.setText("Waiting for service to bind...");
setContentView(view);
setInfoResources(R.string.poa_test, R.string.poa_info, -1);
setPassFailButtonClickListeners();
mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
mReader.setOnImageAvailableListener(this, null);
bindService(new Intent(this, ProjectionService.class), mConnection,
Context.BIND_AUTO_CREATE);
getPassButton().setEnabled(false);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
mReader.close();
}
@Override
protected void onPause() {
super.onPause();
if (mTestStatus == TestStatus.FAILED) {
setTestResultAndFinish(false);
}
}
@Override
public void onImageAvailable(ImageReader reader) {
Log.i(TAG, "onImageAvailable: " + reader);
if (mTimeKeyEventSent != 0
&& mTestStatus == TestStatus.RUNNING
&& mTimeKeyEventSent + RENDERER_DELAY_THRESHOLD < SystemClock.uptimeMillis()) {
mTestStatus = TestStatus.FAILED;
mStatusView.setText("Failed: took too long to render");
}
Image image = reader.acquireLatestImage();
// No new images available
if (image == null) {
Log.w(TAG, "onImageAvailable called but no image!");
return;
}
if (mTestStatus == TestStatus.RUNNING) {
int ret = scanImage(image);
if (ret == -1) {
mStatusView.setText("Failed: saw unexpected color");
getPassButton().setEnabled(false);
mTestStatus = TestStatus.FAILED;
} else if (ret != mPreviousColor && ret == Color.BLUE) {
mStatusView.setText("Success: virtual display rendered expected color");
getPassButton().setEnabled(true);
mTestStatus = TestStatus.PASSED;
}
}
image.close();
}
// modified from the VirtualDisplay Cts test
/**
* Gets the color of the image and ensures all the pixels are the same color
* @param image input image
* @return The color of the image, or -1 for failure
*/
private int scanImage(Image image) {
final Image.Plane plane = image.getPlanes()[0];
final ByteBuffer buffer = plane.getBuffer();
final int width = image.getWidth();
final int height = image.getHeight();
final int pixelStride = plane.getPixelStride();
final int rowStride = plane.getRowStride();
final int rowPadding = rowStride - pixelStride * width;
Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height
+ ", pixelStride=" + pixelStride + ", rowStride=" + rowStride);
int offset = 0;
int blackPixels = 0;
int bluePixels = 0;
int otherPixels = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = 0;
pixel |= (buffer.get(offset) & 0xff) << 16; // R
pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
pixel |= (buffer.get(offset + 2) & 0xff); // B
pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
if (pixel == Color.BLACK || pixel == 0) {
blackPixels += 1;
} else if (pixel == Color.BLUE) {
bluePixels += 1;
} else {
otherPixels += 1;
if (otherPixels < 10) {
Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel));
}
}
offset += pixelStride;
}
offset += rowPadding;
}
// Return a color if it represents all of the pixels.
Log.d(TAG, "- Pixels: " + blackPixels + " black, "
+ bluePixels + " blue, "
+ otherPixels + " other");
if (blackPixels == width * height) {
return Color.BLACK;
} else if (bluePixels == width * height) {
return Color.BLUE;
} else {
return -1;
}
}
}