<!-- Title for location dialog [CHAR LIMIT=60] -->
<string name="dialog_location_title">Turn off location?</string>
<!-- Message body for location dialog [CHAR LIMIT=NONE] -->
- <string name="dialog_location_message" product="default">Your phone can’t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
+ <string name="dialog_location_message" product="default">Your phone can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you\'re not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
<!-- Message body for location dialog [CHAR LIMIT=NONE] -->
- <string name="dialog_location_message" product="tablet">Your tablet can’t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
+ <string name="dialog_location_message" product="tablet">Your tablet can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you\'re not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
<!-- Message body for location dialog [CHAR LIMIT=NONE] -->
- <string name="dialog_location_message" product="device">Your device can’t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
+ <string name="dialog_location_message" product="device">Your device can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you\'re not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
<!-- Text for OK button in location dialog [CHAR LIMIT=30] -->
<string name="dialog_location_ok">Turn off</string>
@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
@VisibleForTesting
Anomaly mAnomaly;
- private AnomalyUtils mAnomalyUtils;
+ @VisibleForTesting
+ AnomalyUtils mAnomalyUtils;
/**
* Listener to give the control back to target fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ initAnomalyUtils();
+ }
+
+ @VisibleForTesting
+ void initAnomalyUtils() {
mAnomalyUtils = AnomalyUtils.getInstance(getContext());
}
.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);
}
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;
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;
}
--- /dev/null
+/*
+ * 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;
+ }
+}
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;
}
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;
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;
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)
.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
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));
+ }
}
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());
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
+
+
+
+