OSDN Git Service

Add background activity toggle in Battery settings
authorjackqdyulei <jackqdyulei@google.com>
Mon, 6 Mar 2017 23:49:04 +0000 (15:49 -0800)
committerjackqdyulei <jackqdyulei@google.com>
Wed, 8 Mar 2017 23:07:24 +0000 (15:07 -0800)
We will show this preference only if the app is target prior to O
(targetSdk < O)

Bug: 33454801
Test: RunSettingsRoboTests
Change-Id: Ibd6a2147b8e7b49d2b7e3176296dd9b91ee4cc6f

res/values/strings.xml
res/xml/power_usage_details.xml
src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java [new file with mode: 0644]
src/com/android/settings/fuelgauge/PowerUsageDetail.java
tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java [new file with mode: 0644]

index c9a1bf7..51fe8b6 100644 (file)
     <!-- Display time remaining until battery is charged [CHAR_LIMIT=60] -->
     <string name="power_charge_remaining"><xliff:g id="until_charged">%1$s</xliff:g> to charge</string>
 
+    <!-- Title for the background activity setting, which allows a user to control whether an app can run in the background [CHAR_LIMIT=40] -->
+    <string name="background_activity_title">Background activity</string>
+    <!-- Summary for the background activity [CHAR_LIMIT=120] -->
+    <string name="background_activity_summary">Allow the app to run in the background</string>
+
     <!-- Title for the screen usage in power use UI [CHAR_LIMIT=40] -->
     <string name="device_screen_usage">Screen usage</string>
     <!-- Title for the screen consumption in power use UI(i.e. Screen consumption: 30% of battery usage) [CHAR_LIMIT=40] -->
index 724aae8..86aa4f5 100644 (file)
             android:key="controls_parent"
             android:title="@string/controls_subtitle">
 
+            <SwitchPreference
+                android:key="background_activity"
+                android:title="@string/background_activity_title"
+                android:summary="@string/background_activity_summary"/>
+
             <Preference
                 android:key="high_power"
                 android:title="@string/high_power_apps" />
diff --git a/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java b/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java
new file mode 100644 (file)
index 0000000..0f08398
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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.settings.fuelgauge;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.util.Log;
+import com.android.settings.core.PreferenceController;
+
+/**
+ * Controller to control whether an app can run in the background
+ */
+public class BackgroundActivityPreferenceController extends PreferenceController implements
+        Preference.OnPreferenceChangeListener {
+
+    private static final String TAG = "BgActivityPrefContr";
+    private static final String KEY_BACKGROUND_ACTIVITY = "background_activity";
+
+    private final PackageManager mPackageManager;
+    private final AppOpsManager mAppOpsManager;
+    private final String[] mPackages;
+    private final int mUid;
+
+    private String mTargetPackage;
+
+    public BackgroundActivityPreferenceController(Context context, int uid) {
+        super(context);
+        mPackageManager = context.getPackageManager();
+        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mUid = uid;
+        mPackages = mPackageManager.getPackagesForUid(mUid);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final int mode = mAppOpsManager
+                .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage);
+
+        if (mode == AppOpsManager.MODE_ERRORED) {
+            preference.setEnabled(false);
+        } else {
+            ((SwitchPreference) preference).setChecked(mode != AppOpsManager.MODE_IGNORED);
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        if (mPackages == null) {
+            return false;
+        }
+        for (final String packageName : mPackages) {
+            if (isLegacyApp(packageName)) {
+                mTargetPackage = packageName;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_BACKGROUND_ACTIVITY;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        boolean switchOn = (Boolean) newValue;
+        mAppOpsManager.setUidMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid,
+                switchOn ? AppOpsManager.MODE_DEFAULT : AppOpsManager.MODE_IGNORED);
+
+        return true;
+    }
+
+    @VisibleForTesting
+    boolean isLegacyApp(final String packageName) {
+        try {
+            ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
+                    PackageManager.GET_META_DATA);
+
+            return info.targetSdkVersion < Build.VERSION_CODES.O;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Cannot find package: " + packageName, e);
+        }
+
+        return false;
+    }
+}
index 94fc3b5..956b279 100644 (file)
@@ -34,7 +34,6 @@ import android.os.BatteryStats;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
-import android.provider.SearchIndexableResource;
 import android.support.v14.preference.PreferenceFragment;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
@@ -67,7 +66,7 @@ import com.android.settings.wifi.WifiSettings;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
 
 public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickListener {
@@ -396,7 +395,11 @@ public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickLi
 
     @Override
     protected List<PreferenceController> getPreferenceControllers(Context context) {
-        return null;
+        final List<PreferenceController> controllers = new ArrayList<>();
+        final int uid = getArguments().getInt(EXTRA_UID, 0);
+        controllers.add(new BackgroundActivityPreferenceController(context, uid));
+
+        return controllers;
     }
 
     @Override
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java
new file mode 100644 (file)
index 0000000..6d3607f
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 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.settings.fuelgauge;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.support.v14.preference.SwitchPreference;
+
+import com.android.settings.TestConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BackgroundActivityPreferenceControllerTest {
+    private static final int UID_NORMAL = 1234;
+    private static final int UID_SPECIAL = 2345;
+    private static final String HIGH_SDK_PACKAGE = "com.android.package.high";
+    private static final String LOW_SDK_PACKAGE = "com.android.package.low";
+    private static final String[] PACKAGES_NORMAL = {LOW_SDK_PACKAGE};
+    private static final String[] PACKAGES_SPECIAL = {HIGH_SDK_PACKAGE, LOW_SDK_PACKAGE};
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
+    private SwitchPreference mPreference;
+    @Mock
+    private ApplicationInfo mHighApplicationInfo;
+    @Mock
+    private ApplicationInfo mLowApplicationInfo;
+    private BackgroundActivityPreferenceController mController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+        when(mPackageManager.getPackagesForUid(UID_NORMAL)).thenReturn(PACKAGES_NORMAL);
+        when(mPackageManager.getPackagesForUid(UID_SPECIAL)).thenReturn(PACKAGES_SPECIAL);
+
+        when(mPackageManager.getApplicationInfo(HIGH_SDK_PACKAGE, PackageManager.GET_META_DATA))
+                .thenReturn(mHighApplicationInfo);
+        when(mPackageManager.getApplicationInfo(LOW_SDK_PACKAGE, PackageManager.GET_META_DATA))
+                .thenReturn(mLowApplicationInfo);
+        mHighApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+        mLowApplicationInfo.targetSdkVersion = Build.VERSION_CODES.L;
+
+        mController = new BackgroundActivityPreferenceController(mContext, UID_NORMAL);
+        mController.isAvailable();
+    }
+
+    @Test
+    public void testOnPreferenceChange_TurnOnCheck_MethodInvoked() {
+        mController.onPreferenceChange(mPreference, true);
+
+        verify(mAppOpsManager).setUidMode(AppOpsManager.OP_RUN_IN_BACKGROUND,
+                UID_NORMAL, AppOpsManager.MODE_DEFAULT);
+    }
+
+    @Test
+    public void testOnPreferenceChange_TurnOffCheck_MethodInvoked() {
+        mController.onPreferenceChange(null, false);
+
+        verify(mAppOpsManager).setUidMode(AppOpsManager.OP_RUN_IN_BACKGROUND,
+                UID_NORMAL, AppOpsManager.MODE_IGNORED);
+    }
+
+    @Test
+    public void testUpdateState_CheckOn_SetCheckedTrue() {
+        when(mAppOpsManager
+                .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(true);
+    }
+
+    @Test
+    public void testUpdateState_CheckOff_SetCheckedFalse() {
+        when(mAppOpsManager
+                .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(false);
+    }
+
+    @Test
+    public void testIsPackageAvailable_SdkLowerThanO_ReturnTrue() {
+        assertThat(mController.isLegacyApp(LOW_SDK_PACKAGE)).isTrue();
+    }
+
+    @Test
+    public void testIsPackageAvailable_SdkLargerOrEqualThanO_ReturnFalse() {
+        assertThat(mController.isLegacyApp(HIGH_SDK_PACKAGE)).isFalse();
+    }
+
+    @Test
+    public void testMultiplePackages_ReturnStatusForTargetPackage() {
+        mController = new BackgroundActivityPreferenceController(mContext, UID_SPECIAL);
+        when(mAppOpsManager
+                .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_SPECIAL, LOW_SDK_PACKAGE))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
+        when(mAppOpsManager
+                .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_SPECIAL, HIGH_SDK_PACKAGE))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+
+        final boolean available = mController.isAvailable();
+        mController.updateState(mPreference);
+
+        assertThat(available).isTrue();
+        // Should get status from LOW_SDK_PACKAGE
+        verify(mPreference).setChecked(true);
+    }
+}