diff --git a/res/values/strings.xml b/res/values/strings.xml index 299c115fa5c..88224609957 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4672,11 +4672,11 @@ Turn off location? - Your phone can’t manage battery normally because %1$s keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app. + Your phone can\'t manage battery normally because %1$s keeps requesting your location when you\'re not using the app.\n\nTo fix this issue, you can turn off location for this app. - Your tablet can’t manage battery normally because %1$s keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app. + Your tablet can\'t manage battery normally because %1$s keeps requesting your location when you\'re not using the app.\n\nTo fix this issue, you can turn off location for this app. - Your device can’t manage battery normally because %1$s keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app. + Your device can\'t manage battery normally because %1$s keeps requesting your location when you\'re not using the app.\n\nTo fix this issue, you can turn off location for this app. Turn off diff --git a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java index 746bd7f0492..2a4282a0789 100644 --- a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java +++ b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java @@ -45,10 +45,12 @@ public class Anomaly implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef({AnomalyActionType.FORCE_STOP, - AnomalyActionType.BACKGROUND_CHECK}) + AnomalyActionType.BACKGROUND_CHECK, + AnomalyActionType.LOCATION_CHECK}) public @interface AnomalyActionType { int FORCE_STOP = 0; int BACKGROUND_CHECK = 1; + int LOCATION_CHECK = 2; } @AnomalyType diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java index 2d0030d0a22..452cc35f30a 100644 --- a/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java +++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java @@ -38,7 +38,8 @@ public class AnomalyDialogFragment extends InstrumentedDialogFragment implements @VisibleForTesting Anomaly mAnomaly; - private AnomalyUtils mAnomalyUtils; + @VisibleForTesting + AnomalyUtils mAnomalyUtils; /** * Listener to give the control back to target fragment @@ -68,6 +69,11 @@ public class AnomalyDialogFragment extends InstrumentedDialogFragment implements @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + initAnomalyUtils(); + } + + @VisibleForTesting + void initAnomalyUtils() { mAnomalyUtils = AnomalyUtils.getInstance(getContext()); } @@ -114,6 +120,14 @@ public class AnomalyDialogFragment extends InstrumentedDialogFragment implements .setPositiveButton(R.string.dialog_background_check_ok, this) .setNegativeButton(R.string.dlg_cancel, null) .create(); + case Anomaly.AnomalyActionType.LOCATION_CHECK: + return new AlertDialog.Builder(context) + .setTitle(R.string.dialog_location_title) + .setMessage(getString(R.string.dialog_location_message, + mAnomaly.displayName)) + .setPositiveButton(R.string.dialog_location_ok, this) + .setNegativeButton(R.string.dlg_cancel, null) + .create(); default: throw new IllegalArgumentException("unknown type " + mAnomaly.type); } diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java index de9f7aad635..9d0e1d0c871 100644 --- a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java +++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java @@ -22,6 +22,7 @@ import android.support.annotation.VisibleForTesting; import com.android.settings.fuelgauge.anomaly.action.AnomalyAction; import com.android.settings.fuelgauge.anomaly.action.BackgroundCheckAction; import com.android.settings.fuelgauge.anomaly.action.ForceStopAction; +import com.android.settings.fuelgauge.anomaly.action.LocationCheckAction; import com.android.settings.fuelgauge.anomaly.checker.AnomalyDetector; import com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector; import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector; @@ -57,8 +58,9 @@ public class AnomalyUtils { case Anomaly.AnomalyType.WAKE_LOCK: return new ForceStopAction(mContext); case Anomaly.AnomalyType.WAKEUP_ALARM: - case Anomaly.AnomalyType.BLUETOOTH_SCAN: return new BackgroundCheckAction(mContext); + case Anomaly.AnomalyType.BLUETOOTH_SCAN: + return new LocationCheckAction(mContext); default: return null; } diff --git a/src/com/android/settings/fuelgauge/anomaly/action/LocationCheckAction.java b/src/com/android/settings/fuelgauge/anomaly/action/LocationCheckAction.java new file mode 100644 index 00000000000..4205b6e702f --- /dev/null +++ b/src/com/android/settings/fuelgauge/anomaly/action/LocationCheckAction.java @@ -0,0 +1,59 @@ +/* + * 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.anomaly.action; + +import android.content.Context; +import android.content.pm.permission.RuntimePermissionPresenter; +import android.support.v4.content.PermissionChecker; + +import com.android.settings.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.fuelgauge.anomaly.Anomaly; +import com.android.settings.overlay.FeatureFactory; + +/** + * Location action for anomaly app, which means to turn off location permission for this app + */ +public class LocationCheckAction implements AnomalyAction { + + private static final String TAG = "LocationCheckAction"; + private static final String LOCATION_PERMISSION = "android.permission-group.LOCATION"; + + private final Context mContext; + private final RuntimePermissionPresenter mRuntimePermissionPresenter; + + public LocationCheckAction(Context context) { + mContext = context; + mRuntimePermissionPresenter = RuntimePermissionPresenter.getInstance(context); + } + + @Override + public void handlePositiveAction(Anomaly anomaly, int metricsKey) { + mRuntimePermissionPresenter.revokeRuntimePermission(anomaly.packageName, + LOCATION_PERMISSION); + } + + @Override + public boolean isActionActive(Anomaly anomaly) { + return PermissionChecker.checkPermission(mContext, LOCATION_PERMISSION, -1, anomaly.uid, + anomaly.packageName) == PermissionChecker.PERMISSION_GRANTED; + } + + @Override + public int getActionType() { + return Anomaly.AnomalyActionType.LOCATION_CHECK; + } +} diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java index 619386e2a1f..4281743bfd5 100644 --- a/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java +++ b/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java @@ -48,15 +48,17 @@ public class BluetoothScanAnomalyDetector implements AnomalyDetector { private Context mContext; public BluetoothScanAnomalyDetector(Context context) { - this(context, new AnomalyDetectionPolicy(context)); + this(context, new AnomalyDetectionPolicy(context), + AnomalyUtils.getInstance(context).getAnomalyAction( + Anomaly.AnomalyType.BLUETOOTH_SCAN)); } @VisibleForTesting - BluetoothScanAnomalyDetector(Context context, AnomalyDetectionPolicy policy) { + BluetoothScanAnomalyDetector(Context context, AnomalyDetectionPolicy policy, + AnomalyAction anomalyAction) { mContext = context; mBatteryUtils = BatteryUtils.getInstance(context); - mAnomalyAction = AnomalyUtils.getInstance(context).getAnomalyAction( - Anomaly.AnomalyType.BLUETOOTH_SCAN); + mAnomalyAction = anomalyAction; mBluetoothScanningThreshold = policy.bluetoothScanThreshold; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java index 365a14a50a2..e8e4bab8b16 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java @@ -18,6 +18,10 @@ package com.android.settings.fuelgauge.anomaly; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; import static org.robolectric.Shadows.shadowOf; import android.app.AlertDialog; @@ -25,12 +29,15 @@ import android.content.Context; import android.content.DialogInterface; import com.android.settings.R; +import com.android.settings.fuelgauge.anomaly.action.AnomalyAction; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowAlertDialog; @@ -43,13 +50,21 @@ public class AnomalyDialogFragmentTest { private static final String PACKAGE_NAME = "com.android.app"; private static final String DISPLAY_NAME = "app"; private static final int UID = 111; + + @Mock + private AnomalyUtils mAnomalyUtils; + @Mock + private AnomalyAction mAnomalyAction; private Anomaly mWakeLockAnomaly; private Anomaly mWakeupAlarmAnomaly; + private Anomaly mBluetoothAnomaly; private AnomalyDialogFragment mAnomalyDialogFragment; private Context mContext; @Before public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; mWakeLockAnomaly = new Anomaly.Builder() .setType(Anomaly.AnomalyType.WAKE_LOCK) @@ -63,6 +78,12 @@ public class AnomalyDialogFragmentTest { .setPackageName(PACKAGE_NAME) .setDisplayName(DISPLAY_NAME) .build(); + mBluetoothAnomaly = new Anomaly.Builder() + .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN) + .setUid(UID) + .setPackageName(PACKAGE_NAME) + .setDisplayName(DISPLAY_NAME) + .build(); } @Test @@ -114,4 +135,29 @@ public class AnomalyDialogFragmentTest { assertThat(dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText()).isEqualTo( mContext.getString(R.string.dlg_cancel)); } + + @Test + public void testOnCreateDialog_bluetoothAnomaly_fireLocationCheckDialog() { + mAnomalyDialogFragment = spy(AnomalyDialogFragment.newInstance(mBluetoothAnomaly, + 0 /* metricskey */)); + mAnomalyDialogFragment.mAnomalyUtils = mAnomalyUtils; + doReturn(mAnomalyAction).when(mAnomalyUtils).getAnomalyAction(anyInt()); + doNothing().when(mAnomalyDialogFragment).initAnomalyUtils(); + doReturn(Anomaly.AnomalyActionType.LOCATION_CHECK).when(mAnomalyAction).getActionType(); + + FragmentTestUtil.startFragment(mAnomalyDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.dialog_location_message, + mWakeLockAnomaly.displayName)); + assertThat(shadowDialog.getTitle()).isEqualTo( + mContext.getString(R.string.dialog_location_title)); + assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).getText()).isEqualTo( + mContext.getString(R.string.dialog_location_ok)); + assertThat(dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText()).isEqualTo( + mContext.getString(R.string.dlg_cancel)); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java index 941e9cd2bf5..a687e2f49e1 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java @@ -106,7 +106,8 @@ public class BluetoothScanAnomalyDetectorTest { mUsageList.add(mTargetSipper); doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList(); - mBluetoothScanAnomalyDetector = spy(new BluetoothScanAnomalyDetector(mContext, mPolicy)); + mBluetoothScanAnomalyDetector = spy( + new BluetoothScanAnomalyDetector(mContext, mPolicy, mAnomalyAction)); mBluetoothScanAnomalyDetector.mBatteryUtils = mBatteryUtils; mBluetoothScanAnomalyDetector.mAnomalyAction = mAnomalyAction; doReturn(false).when(mBatteryUtils).shouldHideSipper(any()); diff --git a/tests/unit/src/com/android/settings/fuelgauge/anomaly/action/LocationCheckActionTest.java b/tests/unit/src/com/android/settings/fuelgauge/anomaly/action/LocationCheckActionTest.java new file mode 100644 index 00000000000..8be3320a88d --- /dev/null +++ b/tests/unit/src/com/android/settings/fuelgauge/anomaly/action/LocationCheckActionTest.java @@ -0,0 +1,71 @@ +/* + * 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.anomaly.action; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.settings.fuelgauge.anomaly.Anomaly; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LocationCheckActionTest { + private static final String PACKAGE_NAME = "com.android.chrome"; + + private Context mContext; + private LocationCheckAction mLocationCheckAction; + private Anomaly mAnomaly; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mLocationCheckAction = new LocationCheckAction(mContext); + + mAnomaly = new Anomaly.Builder() + .setUid(getPackageUid(mContext, PACKAGE_NAME)) + .setPackageName(PACKAGE_NAME) + .build(); + } + + @Test + public void testRevokeAndCheck() { + mLocationCheckAction.handlePositiveAction(mAnomaly, 0 /* metric key */); + + assertThat(mLocationCheckAction.isActionActive(mAnomaly)).isFalse(); + } + + private int getPackageUid(Context context, String packageName) { + try { + return context.getPackageManager().getPackageUid(packageName, + PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + return -1; + } + } +} + + + +