Add CTS-V test for Battery Saver UI affordance.

This is for CDD requirement 8.3/ H-1-1.

Bug: 123045487
Test: install on device and manually check activities
Change-Id: I6947c57c24abf38623ff2207f688966a07e8011e
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 08f52f1..167248d 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -156,6 +156,17 @@
             android:theme="@style/OverlayTheme"
             android:label="Overlaying Activity"/>
 
+        <activity
+            android:name=".battery.BatterySaverTestActivity"
+            android:label="@string/battery_saver_test"
+            android:configChanges="keyboardHidden|orientation|screenSize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_other" />
+        </activity>
+
         <activity android:name=".forcestop.RecentTaskRemovalTestActivity"
                   android:label="@string/remove_from_recents_test"
                   android:configChanges="keyboardHidden|orientation|screenSize">
diff --git a/apps/CtsVerifier/res/layout/ordered_test.xml b/apps/CtsVerifier/res/layout/ordered_test.xml
new file mode 100644
index 0000000..1c0b029
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ordered_test.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/txt_instruction"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/InstructionsSmallFont"
+        android:layout_alignParentTop="true" />
+
+    <Button
+        android:id="@+id/btn_next"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/next_button_text"
+        android:layout_below="@id/txt_instruction" />
+
+    <include android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons" />
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/screen_pinning.xml b/apps/CtsVerifier/res/layout/screen_pinning.xml
deleted file mode 100644
index f1d7b65..0000000
--- a/apps/CtsVerifier/res/layout/screen_pinning.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <TextView
-        android:id="@+id/error_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
-
-    <ScrollView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true" >
-
-        <RelativeLayout
-            android:id="@+id/instructions_group"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical" >
-
-            <LinearLayout
-                android:id="@+id/instructions_list"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="vertical" >
-
-            </LinearLayout>
-
-            <Button
-                android:id="@+id/next_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentEnd="true"
-                android:layout_below="@id/instructions_list"
-                android:text="@string/next_button_text" />
-
-        </RelativeLayout>
-    </ScrollView>
-
-    <include
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        layout="@layout/pass_fail_buttons" />
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 4b9afec..64930a0 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -23,6 +23,7 @@
     <string name="fail_button_text">Fail</string>
     <string name="next_button_text">Next</string>
     <string name="go_button_text">Go</string>
+    <string name="tests_completed_successfully">All tests completed successfully.</string>
 
     <!-- Strings for TestListActivity -->
     <string name="test_category_audio">Audio</string>
@@ -70,6 +71,35 @@
     <string name="bu_generate_error">Error occurred while generating test data...</string>
     <string name="bu_settings">Settings</string>
 
+    <!-- Strings for BatterySaverTestActivity -->
+    <string name="battery_saver_test">Battery Saver Test</string>
+    <string name="battery_saver_test_info">
+        This test verifies that the device provides a user affordance to enable and disable
+        Battery Saver feature.
+    </string>
+    <string name="battery_saver_test_no_battery_detected">
+        No battery detected. This test will only work on devices that contain a battery.
+    </string>
+    <string name="battery_saver_test_unplug">
+        Unplug the device or run \"adb shell dumpsys battery unplug\".
+    </string>
+    <string name="battery_saver_test_device_plugged_in">
+        The device is still plugged in.
+    </string>
+    <string name="battery_saver_test_enable_bs">
+        Look for a button that allows you to turn Battery Saver on and off. The button may be in the
+        Quick Settings section or on the Battery page of device settings. Use the button to turn
+        Battery Saver on.
+    </string>
+    <string name="battery_saver_test_disable_bs">
+        Now use the button to turn Battery Saver off.
+    </string>
+    <string name="battery_saver_test_start_turn_bs_off">
+        Turn Battery Saver off to begin the test.
+    </string>
+    <string name="battery_saver_test_bs_on">Battery Saver is on.</string>
+    <string name="battery_saver_test_bs_off">Battery Saver is off.</string>
+
     <!-- Strings for Device Administration tests -->
     <string name="da_policy_serialization_test">Policy Serialization Test</string>
     <string name="da_policy_serialization_info">This test checks that a device policy is properly
@@ -4346,7 +4376,6 @@
     <string name="screen_pin_check_pinned">Press Next to verify the app is pinned.</string>
     <string name="screen_pin_no_exit">Try to leave the app without unpinning the screen. Press next once you have verified you cannot leave.</string>
     <string name="screen_pin_exit">Use interactions defined by your device to unpin such as long pressing the back and overview button, then press next.</string>
-    <string name="screen_pinning_done">All tests completed successfully.</string>
 
     <string name="error_screen_no_longer_pinned">The screen was no longer pinned.</string>
     <string name="error_screen_already_pinned">Cannot start the test with the screen already pinned.</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java
new file mode 100644
index 0000000..d24fdb7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * {@link PassFailButtons.Activity} that supports showing a series of tests in order.
+ */
+public abstract class OrderedTestActivity extends PassFailButtons.Activity {
+    private static final String KEY_CURRENT_TEST = "current_test";
+
+    private Test[] mTests;
+    private int mTestIndex;
+
+    private Button mNextButton;
+    private TextView mInstructions;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ordered_test);
+        setPassFailButtonClickListeners();
+
+        mTests = getTests();
+
+        mInstructions = findViewById(R.id.txt_instruction);
+
+        mNextButton = findViewById(R.id.btn_next);
+        mNextButton.setOnClickListener(v -> {
+            if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
+                mTests[mTestIndex].onNextClick();
+            }
+        });
+
+        // Don't allow pass until all tests complete.
+        findViewById(R.id.pass_button).setVisibility(View.GONE);
+
+        // Figure out if we are in a test or starting from the beginning.
+        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
+            mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
+        } else {
+            mTestIndex = 0;
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        mTests[mTestIndex].run();
+    }
+
+    /** Returns a list of tests to run in order. */
+    protected abstract Test[] getTests();
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+    }
+
+    protected void succeed() {
+        runOnUiThread(() -> {
+            mTestIndex++;
+            if (mTestIndex < mTests.length) {
+                mTests[mTestIndex].run();
+            } else {
+                // On test completion, hide "next" and "fail" buttons, and show "pass" button
+                // instead.
+                mInstructions.setText(R.string.tests_completed_successfully);
+                mNextButton.setVisibility(View.GONE);
+                findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+                findViewById(R.id.fail_button).setVisibility(View.GONE);
+            }
+        });
+    }
+
+    protected void error(int stringResId) {
+        Toast.makeText(this, stringResId, Toast.LENGTH_SHORT).show();
+    }
+
+    protected abstract class Test {
+        private final int mStringId;
+
+        public Test(int stringResId) {
+            mStringId = stringResId;
+        }
+
+        protected void run() {
+            showText();
+        }
+
+        protected void showText() {
+            if (mStringId == 0) {
+                return;
+            }
+            mInstructions.setText(mStringId);
+        }
+
+        protected void onNextClick() {
+        }
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java
new file mode 100644
index 0000000..f0dc540
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 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.battery;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.view.View;
+
+import com.android.cts.verifier.OrderedTestActivity;
+import com.android.cts.verifier.R;
+
+public class BatterySaverTestActivity extends OrderedTestActivity {
+    private PowerManager mPowerManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setInfoResources(R.string.battery_saver_test, R.string.battery_saver_test_info, -1);
+
+        mPowerManager = getSystemService(PowerManager.class);
+    }
+
+    @Override
+    protected OrderedTestActivity.Test[] getTests() {
+        return new Test[]{
+                // Confirm a battery exists before proceeding with the rest of the tests,
+                mConfirmBatteryExists,
+                // Verify device is unplugged.
+                mConfirmUnplugged,
+                // Verify battery saver is off.
+                mConfirmBsOff,
+                mEnableBs,
+                mDisableBs
+        };
+    }
+
+    private final Test mConfirmBatteryExists = new Test(
+            R.string.battery_saver_test_no_battery_detected) {
+        @Override
+        protected void run() {
+            super.run();
+
+            final Intent batteryInfo = registerReceiver(null, new
+                    IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+            if (batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true)) {
+                succeed();
+            } else {
+                findViewById(R.id.btn_next).setVisibility(View.GONE);
+                // Set both pass and fail to visible in case the device is meant to have a battery.
+                findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+                findViewById(R.id.fail_button).setVisibility(View.VISIBLE);
+            }
+        }
+    };
+
+    private final Test mConfirmUnplugged = new Test(R.string.battery_saver_test_unplug) {
+        @Override
+        protected void run() {
+            super.run();
+
+            if (!isPluggedIn()) {
+                succeed();
+            }
+        }
+
+        @Override
+        protected void onNextClick() {
+            if (isPluggedIn()) {
+                error(R.string.battery_saver_test_device_plugged_in);
+            } else {
+                succeed();
+            }
+        }
+
+        private boolean isPluggedIn() {
+            Intent intent = registerReceiver(null,
+                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+            int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+            return plugged == BatteryManager.BATTERY_PLUGGED_AC
+                    || plugged == BatteryManager.BATTERY_PLUGGED_USB
+                    || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+        }
+    };
+
+    private final Test mConfirmBsOff = new Test(R.string.battery_saver_test_start_turn_bs_off) {
+        @Override
+        protected void run() {
+            super.run();
+
+            if (!mPowerManager.isPowerSaveMode()) {
+                succeed();
+            }
+        }
+
+        @Override
+        protected void onNextClick() {
+            if (mPowerManager.isPowerSaveMode()) {
+                error(R.string.battery_saver_test_bs_on);
+            } else {
+                succeed();
+            }
+        }
+    };
+
+    private final Test mEnableBs = new Test(R.string.battery_saver_test_enable_bs) {
+        @Override
+        protected void onNextClick() {
+            if (mPowerManager.isPowerSaveMode()) {
+                succeed();
+            } else {
+                error(R.string.battery_saver_test_bs_off);
+            }
+        }
+    };
+
+    private final Test mDisableBs = new Test(R.string.battery_saver_test_disable_bs) {
+        @Override
+        protected void onNextClick() {
+            if (!mPowerManager.isPowerSaveMode()) {
+                succeed();
+            } else {
+                error(R.string.battery_saver_test_bs_on);
+            }
+        }
+    };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
index a6e5f98..180306e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
@@ -18,78 +18,40 @@
 import android.app.ActivityManager;
 import android.os.Bundle;
 import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.OrderedTestActivity;
 import com.android.cts.verifier.R;
 
-public class ScreenPinningTestActivity extends PassFailButtons.Activity {
+public class ScreenPinningTestActivity extends OrderedTestActivity {
 
     private static final String TAG = "ScreenPinningTestActivity";
-    private static final String KEY_CURRENT_TEST = "keyCurrentTest";
     private static final long TASK_MODE_CHECK_DELAY = 200;
     private static final int MAX_TASK_MODE_CHECK_COUNT = 5;
 
-    private Test[] mTests;
-    private int mTestIndex;
-
     private ActivityManager mActivityManager;
-    private Button mNextButton;
-    private LinearLayout mInstructions;
 
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.screen_pinning);
-        setPassFailButtonClickListeners();
-
-        mTests = new Test[] {
-            // Verify not already pinned.
-            mCheckStartedUnpinned,
-            // Enter pinning, verify pinned, try leaving and have the user exit.
-            mCheckStartPinning,
-            mCheckIsPinned,
-            mCheckTryLeave,
-            mCheckUnpin,
-            // Enter pinning, verify pinned, and use APIs to exit.
-            mCheckStartPinning,
-            mCheckIsPinned,
-            mCheckUnpinFromCode,
-            // All done.
-            mDone,
-        };
 
         mActivityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
-        mInstructions = (LinearLayout) findViewById(R.id.instructions_list);
-
-        mNextButton = (Button) findViewById(R.id.next_button);
-        mNextButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
-                    mTests[mTestIndex].onNextClick();
-                }
-            }
-        });
-
-        // Don't allow pass until all tests complete.
-        findViewById(R.id.pass_button).setVisibility(View.GONE);
-
-        // Figure out if we are in a test or starting from the beginning.
-        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
-            mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
-        } else {
-            mTestIndex = 0;
-        }
-        mTests[mTestIndex].run();
-    };
+    }
 
     @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+    protected Test[] getTests() {
+        return new Test[]{
+                // Verify not already pinned.
+                mCheckStartedUnpinned,
+                // Enter pinning, verify pinned, try leaving and have the user exit.
+                mCheckStartPinning,
+                mCheckIsPinned,
+                mCheckTryLeave,
+                mCheckUnpin,
+                // Enter pinning, verify pinned, and use APIs to exit.
+                mCheckStartPinning,
+                mCheckIsPinned,
+                mCheckUnpinFromCode
+        };
     }
 
     @Override
@@ -98,27 +60,8 @@
         // Users can still leave by pressing fail (or when done the pass) button.
     }
 
-    private void show(int id) {
-        TextView tv = new TextView(this);
-        tv.setPadding(10, 10, 10, 30);
-        tv.setText(id);
-        mInstructions.removeAllViews();
-        mInstructions.addView(tv);
-    }
-
-    private void succeed() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mTestIndex++;
-                if (mTestIndex < mTests.length) {
-                    mTests[mTestIndex].run();
-                }
-            }
-        });
-    }
-
-    private void error(int errorId) {
+    @Override
+    protected void error(int errorId) {
         error(errorId, new Throwable());
     }
 
@@ -128,10 +71,8 @@
             public void run() {
                 String error = getString(errorId);
                 Log.d(TAG, error, cause);
-                // No more instructions needed.
-                findViewById(R.id.instructions_group).setVisibility(View.GONE);
 
-                ((TextView) findViewById(R.id.error_text)).setText(error);
+                ((TextView) findViewById(R.id.txt_instruction)).setText(error);
             }
         });
     }
@@ -211,40 +152,6 @@
                     error(R.string.error_screen_pinning_couldnt_exit);
                 }
             }
-        };
+        }
     };
-
-    private final Test mDone = new Test(R.string.screen_pinning_done) {
-        @Override
-        protected void run() {
-            super.run();
-            // On test completion, hide "next" button, and show "pass" button
-            // instead.
-            mNextButton.setVisibility(View.GONE);
-            findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
-        };
-    };
-
-    private abstract class Test {
-        private final int mResId;
-
-        public Test(int showId) {
-            mResId = showId;
-        }
-
-        protected void run() {
-            showText();
-        }
-
-        public void showText() {
-            if (mResId == 0) {
-                return;
-            }
-            show(mResId);
-        }
-
-        protected void onNextClick() {
-        }
-    }
-
 }