blob: 2956ed7b54d94104d6ee76b052b62fe33a299b86 [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.sensors.helpers;
import com.android.cts.verifier.os.TimeoutResetActivity;
import com.android.cts.verifier.sensors.base.BaseSensorTestActivity;
import com.android.cts.verifier.sensors.base.ISensorTestStateContainer;
import android.app.Activity;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
* A class that provides functionality to manipulate the state of the device's screen.
*
* The implementation uses a simple state machine with 3 states: keep-screen-off, keep-screen-on,
* and a free-state where the class does not affect the system's state.
*
* The list of transitions and their handlers are:
* keep-screen-on --(turnScreenOff)--> keep-screen-off
* keep-screen-on --(releaseScreenOn)--> free-state
*
* keep-screen-off --(turnScreenOn)--> keep-screen-on
* keep-screen-off --(wakeUpScreen)--> free-state
*
* free-state --(turnScreenOff)--> keep-screen-off
* free-state --(turnScreenOn)--> keep-screen-on
*
* NOTES:
* - the operator still can turn on/off the screen by pressing the power button
* - this class must be used by a single client, that can manage the state of the instance, likely
* - in a single-threaded environment
*/
public class SensorTestScreenManipulator {
private static final String TAG = SensorTestScreenManipulator.class.getSimpleName();
private final Activity mActivity;
private final DevicePolicyManager mDevicePolicyManager;
private final ComponentName mComponentName;
private final PowerManager.WakeLock mWakeUpScreenWakeLock;
private final PowerManager.WakeLock mKeepScreenOnWakeLock;
private InternalBroadcastReceiver mBroadcastReceiver;
private boolean mTurnOffScreenOnPowerDisconnected;
public SensorTestScreenManipulator(Activity activity) {
mActivity = activity;
mComponentName = SensorDeviceAdminReceiver.getComponentName(activity);
mDevicePolicyManager =
(DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
int levelAndFlags = PowerManager.FULL_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE
| PowerManager.ACQUIRE_CAUSES_WAKEUP;
PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
mWakeUpScreenWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestWakeUpScreen");
mWakeUpScreenWakeLock.setReferenceCounted(false);
mKeepScreenOnWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestKeepScreenOn");
mWakeUpScreenWakeLock.setReferenceCounted(false);
}
/**
* Initializes the current instance.
* Initialization should usually happen inside {@link BaseSensorTestActivity#activitySetUp}.
*
* NOTE: Initialization will bring up an Activity to let the user activate the Device Admin,
* this method will block until the user completes the operation.
*/
public synchronized void initialize(ISensorTestStateContainer stateContainer)
throws InterruptedException {
if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mComponentName);
int resultCode = stateContainer.executeActivity(intent);
if (resultCode != Activity.RESULT_OK) {
throw new IllegalStateException(
"Test cannot execute without Activating the Device Administrator.");
}
}
if (mBroadcastReceiver == null) {
mBroadcastReceiver = new InternalBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
mActivity.registerReceiver(mBroadcastReceiver, intentFilter);
}
}
/**
* Closes the current instance.
* This operation should usually happen inside {@link BaseSensorTestActivity#activityCleanUp}.
*/
public synchronized void close() {
if (mBroadcastReceiver != null) {
mActivity.unregisterReceiver(mBroadcastReceiver);
mBroadcastReceiver = null;
}
}
/**
* Instruct the device to turn off the screen immediately.
*/
public synchronized void turnScreenOff() {
ensureDeviceAdminInitialized();
final CountDownLatch screenOffSignal = new CountDownLatch(1);
BroadcastReceiver screenOffBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mActivity.unregisterReceiver(this);
screenOffSignal.countDown();
}
};
mActivity.registerReceiver(
screenOffBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
releaseScreenOn();
if (hasDeviceAdminFeature()) {
mDevicePolicyManager.lockNow();
} else {
TimeoutResetActivity.turnOffScreen(mActivity);
}
try {
screenOffSignal.await();
} catch (InterruptedException e) {
Log.wtf(TAG, "error waiting for screen off signal", e);
}
}
/**
* Instruct the device to wake up the screen immediately, the screen will remain on for a bit,
* but the system might turn the screen off in the near future.
*/
public synchronized void wakeUpScreen() {
mWakeUpScreenWakeLock.acquire();
// release right away, the screen still remains on for a bit, but not indefinitely
mWakeUpScreenWakeLock.release();
}
/**
* Instructs the device to turn on the screen immediately.
*
* The screen will remain on until the client invokes {@link #releaseScreenOn()}, or the user
* presses the device's power button.
*/
public synchronized void turnScreenOn() {
if (mKeepScreenOnWakeLock.isHeld()) {
// recover from cases when we could get out of sync, this can happen because the user
// can press the power button, and other wake-locks can prevent intents to be received
mKeepScreenOnWakeLock.release();
}
mKeepScreenOnWakeLock.acquire();
}
/**
* Indicates that the client does not require the screen to remain on anymore.
*
* See {@link #turnScreenOn()} for more information.
*/
public synchronized void releaseScreenOn() {
if (!mKeepScreenOnWakeLock.isHeld()) {
return;
}
mKeepScreenOnWakeLock.release();
}
/**
* Queues a request to turn off the screen off when the device has been disconnected from a
* power source (usually upon USB disconnected).
*
* (It is useful for Sensor Power Tests, as the Power Monitor usually detaches itself from the
* device before beginning to sample data).
*/
public synchronized void turnScreenOffOnNextPowerDisconnect() {
ensureDeviceAdminInitialized();
mTurnOffScreenOnPowerDisconnected = true;
}
private void ensureDeviceAdminInitialized() throws IllegalStateException {
if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
throw new IllegalStateException("Component must be initialized before it can be used.");
}
}
private boolean isDeviceAdminInitialized() {
if (!mDevicePolicyManager.isAdminActive(mComponentName)) {
return false;
}
return mDevicePolicyManager
.hasGrantedPolicy(mComponentName, DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
}
private boolean hasDeviceAdminFeature() {
return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
}
private class InternalBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, Intent.ACTION_POWER_DISCONNECTED)) {
if (mTurnOffScreenOnPowerDisconnected) {
turnScreenOff();
// reset the flag after it has triggered once, we try to avoid cases when the test
// might leave the receiver enabled after itself,
// this approach still provides a way to multiplex one time requests
mTurnOffScreenOnPowerDisconnected = false;
}
}
}
}
}