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.sensors.helpers;
18 
19 import com.android.cts.verifier.os.TimeoutResetActivity;
20 import com.android.cts.verifier.sensors.base.BaseSensorTestActivity;
21 import com.android.cts.verifier.sensors.base.ISensorTestStateContainer;
22 
23 import android.app.Activity;
24 import android.app.admin.DeviceAdminInfo;
25 import android.app.admin.DevicePolicyManager;
26 import android.content.BroadcastReceiver;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.PackageManager;
32 import android.os.PowerManager;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import java.util.concurrent.CountDownLatch;
37 
38 /**
39  * A class that provides functionality to manipulate the state of the device's screen.
40  *
41  * The implementation uses a simple state machine with 3 states: keep-screen-off, keep-screen-on,
42  * and a free-state where the class does not affect the system's state.
43  *
44  * The list of transitions and their handlers are:
45  *      keep-screen-on --(turnScreenOff)--> keep-screen-off
46  *      keep-screen-on --(releaseScreenOn)--> free-state
47  *
48  *      keep-screen-off --(turnScreenOn)--> keep-screen-on
49  *      keep-screen-off --(wakeUpScreen)--> free-state
50  *
51  *      free-state --(turnScreenOff)--> keep-screen-off
52  *      free-state --(turnScreenOn)--> keep-screen-on
53  *
54  * NOTES:
55  * - the operator still can turn on/off the screen by pressing the power button
56  * - this class must be used by a single client, that can manage the state of the instance, likely
57  * - in a single-threaded environment
58  */
59 public class SensorTestScreenManipulator {
60     private static final String TAG = SensorTestScreenManipulator.class.getSimpleName();
61 
62     private final Activity mActivity;
63     private final DevicePolicyManager mDevicePolicyManager;
64     private final ComponentName mComponentName;
65     private final PowerManager.WakeLock mWakeUpScreenWakeLock;
66     private final PowerManager.WakeLock mKeepScreenOnWakeLock;
67 
68     private InternalBroadcastReceiver mBroadcastReceiver;
69     private boolean mTurnOffScreenOnPowerDisconnected;
70 
71 
SensorTestScreenManipulator(Activity activity)72     public SensorTestScreenManipulator(Activity activity) {
73         mActivity = activity;
74         mComponentName = SensorDeviceAdminReceiver.getComponentName(activity);
75         mDevicePolicyManager =
76                 (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
77 
78         int levelAndFlags = PowerManager.FULL_WAKE_LOCK
79                 | PowerManager.ON_AFTER_RELEASE
80                 | PowerManager.ACQUIRE_CAUSES_WAKEUP;
81         PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
82         mWakeUpScreenWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestWakeUpScreen");
83         mWakeUpScreenWakeLock.setReferenceCounted(false);
84         mKeepScreenOnWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestKeepScreenOn");
85         mKeepScreenOnWakeLock.setReferenceCounted(false);
86     }
87 
88     /**
89      * Initializes the current instance.
90      * Initialization should usually happen inside {@link BaseSensorTestActivity#activitySetUp}.
91      *
92      * NOTE: Initialization will bring up an Activity to let the user activate the Device Admin,
93      * this method will block until the user completes the operation.
94      */
initialize(ISensorTestStateContainer stateContainer)95     public synchronized void initialize(ISensorTestStateContainer stateContainer)
96             throws InterruptedException {
97         if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
98             Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
99             intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mComponentName);
100             int resultCode = stateContainer.executeActivity(intent);
101             if (resultCode != Activity.RESULT_OK) {
102                 throw new IllegalStateException(
103                         "Test cannot execute without Activating the Device Administrator.");
104             }
105         }
106 
107         if (mBroadcastReceiver == null) {
108             mBroadcastReceiver = new InternalBroadcastReceiver();
109             IntentFilter intentFilter = new IntentFilter();
110             intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
111             mActivity.registerReceiver(mBroadcastReceiver, intentFilter);
112         }
113     }
114 
115     /**
116      * Closes the current instance.
117      * This operation should usually happen inside {@link BaseSensorTestActivity#activityCleanUp}.
118      */
close()119     public synchronized  void close() {
120         if (mBroadcastReceiver != null) {
121             mActivity.unregisterReceiver(mBroadcastReceiver);
122             mBroadcastReceiver = null;
123         }
124     }
125 
126     /**
127      * Instruct the device to turn off the screen immediately.
128      */
turnScreenOff()129     public synchronized void turnScreenOff() {
130         ensureDeviceAdminInitialized();
131 
132         final CountDownLatch screenOffSignal = new CountDownLatch(1);
133         BroadcastReceiver screenOffBroadcastReceiver = new BroadcastReceiver() {
134             @Override
135             public void onReceive(Context context, Intent intent) {
136                 mActivity.unregisterReceiver(this);
137                 screenOffSignal.countDown();
138             }
139         };
140         mActivity.registerReceiver(
141                 screenOffBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
142 
143         releaseScreenOn();
144         if (hasDeviceAdminFeature()) {
145             mDevicePolicyManager.lockNow();
146         } else {
147             TimeoutResetActivity.turnOffScreen(mActivity);
148         }
149 
150         try {
151             screenOffSignal.await();
152         } catch (InterruptedException e) {
153             Log.wtf(TAG, "error waiting for screen off signal", e);
154         }
155     }
156 
157     /**
158      * Instruct the device to wake up the screen immediately, the screen will remain on for a bit,
159      * but the system might turn the screen off in the near future.
160      */
wakeUpScreen()161     public synchronized void wakeUpScreen() {
162         mWakeUpScreenWakeLock.acquire();
163         // release right away, the screen still remains on for a bit, but not indefinitely
164         mWakeUpScreenWakeLock.release();
165     }
166 
167     /**
168      * Instructs the device to turn on the screen immediately.
169      *
170      * The screen will remain on until the client invokes {@link #releaseScreenOn()}, or the user
171      * presses the device's power button.
172      */
turnScreenOn()173     public synchronized void turnScreenOn() {
174         if (mKeepScreenOnWakeLock.isHeld()) {
175             // recover from cases when we could get out of sync, this can happen because the user
176             // can press the power button, and other wake-locks can prevent intents to be received
177             mKeepScreenOnWakeLock.release();
178         }
179         mKeepScreenOnWakeLock.acquire();
180     }
181 
182     /**
183      * Indicates that the client does not require the screen to remain on anymore.
184      *
185      * See {@link #turnScreenOn()} for more information.
186      */
releaseScreenOn()187     public synchronized void releaseScreenOn() {
188         if (!mKeepScreenOnWakeLock.isHeld()) {
189             return;
190         }
191         mKeepScreenOnWakeLock.release();
192     }
193 
194     /**
195      * Queues a request to turn off the screen off when the device has been disconnected from a
196      * power source (usually upon USB disconnected).
197      *
198      * (It is useful for Sensor Power Tests, as the Power Monitor usually detaches itself from the
199      * device before beginning to sample data).
200      */
turnScreenOffOnNextPowerDisconnect()201     public synchronized void turnScreenOffOnNextPowerDisconnect() {
202         ensureDeviceAdminInitialized();
203         mTurnOffScreenOnPowerDisconnected = true;
204     }
205 
ensureDeviceAdminInitialized()206     private void ensureDeviceAdminInitialized() throws IllegalStateException {
207         if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
208             throw new IllegalStateException("Component must be initialized before it can be used.");
209         }
210     }
211 
isDeviceAdminInitialized()212     private boolean isDeviceAdminInitialized() {
213         if (!mDevicePolicyManager.isAdminActive(mComponentName)) {
214             return false;
215         }
216         return mDevicePolicyManager
217                 .hasGrantedPolicy(mComponentName, DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
218     }
219 
hasDeviceAdminFeature()220     private boolean hasDeviceAdminFeature() {
221         return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
222     }
223 
224     private class InternalBroadcastReceiver extends BroadcastReceiver {
225         @Override
onReceive(Context context, Intent intent)226         public void onReceive(Context context, Intent intent) {
227             String action = intent.getAction();
228 
229             if (TextUtils.equals(action, Intent.ACTION_POWER_DISCONNECTED)) {
230                 if (mTurnOffScreenOnPowerDisconnected) {
231                     turnScreenOff();
232                     // reset the flag after it has triggered once, we try to avoid cases when the test
233                     // might leave the receiver enabled after itself,
234                     // this approach still provides a way to multiplex one time requests
235                     mTurnOffScreenOnPowerDisconnected = false;
236                 }
237             }
238         }
239     }
240 }
241