From dbaea5af63faaec8ffd34cbb8477ae86c76b9102 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 31 Jan 2018 14:26:42 -0800 Subject: [PATCH 1/9] Clean up WifiTetherPreferenceController In previous code there are two main issues: 1. It listens to update from both WIFI_AP_STATE_CHANGED_ACTION and ACTION_TETHER_STATE_CHANGED. It is unnecessary because they provides same info(whether wifi hotspot is enabled, enabling...) 2. New API softApCallback already covers the WIFI_AP_STATE_CHANGED_ACTION, so we don't need this broadcast anymore. This cl fixes those two issues by cleaning up BroadcastReceiver and update the tests. Bug: 72702183 Test: RunSettingsRoboTests Change-Id: I21c2818e0f0185172f34447a1716dc47ee065e23 --- .../WifiTetherPreferenceController.java | 63 ++--------- .../tether/WifiTetherSwitchBarController.java | 11 +- .../WifiTetherPreferenceControllerTest.java | 104 ++++++------------ 3 files changed, 51 insertions(+), 127 deletions(-) diff --git a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java index 11f1f5977d6..826ed426019 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java @@ -39,13 +39,12 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; -import java.util.List; - public class WifiTetherPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop { - public static final IntentFilter WIFI_TETHER_INTENT_FILTER; private static final String WIFI_TETHER_SETTINGS = "wifi_tether"; + private static final IntentFilter AIRPLANE_INTENT_FILTER = new IntentFilter( + Intent.ACTION_AIRPLANE_MODE_CHANGED); private final ConnectivityManager mConnectivityManager; private final String[] mWifiRegexs; @@ -58,12 +57,6 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController @VisibleForTesting WifiTetherSoftApManager mWifiTetherSoftApManager; - static { - WIFI_TETHER_INTENT_FILTER = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - WIFI_TETHER_INTENT_FILTER.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); - WIFI_TETHER_INTENT_FILTER.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); - } - public WifiTetherPreferenceController(Context context, Lifecycle lifecycle) { this(context, lifecycle, true /* initSoftApManager */); } @@ -113,7 +106,7 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController @Override public void onStart() { if (mPreference != null) { - mContext.registerReceiver(mReceiver, WIFI_TETHER_INTENT_FILTER); + mContext.registerReceiver(mReceiver, AIRPLANE_INTENT_FILTER); clearSummaryForAirplaneMode(); if (mWifiTetherSoftApManager != null) { mWifiTetherSoftApManager.registerSoftApCallback(); @@ -140,6 +133,7 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController @Override public void onStateChanged(int state, int failureReason) { mSoftApState = state; + handleWifiApStateChanged(state, failureReason); } @Override @@ -162,34 +156,21 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(action)) { - int state = intent.getIntExtra( - WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED); - int reason = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, - WifiManager.SAP_START_FAILURE_GENERAL); - handleWifiApStateChanged(state, reason); - } else if (ConnectivityManager.ACTION_TETHER_STATE_CHANGED.equals(action)) { - List active = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_ACTIVE_TETHER); - List errored = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_ERRORED_TETHER); - updateTetherState(active.toArray(), errored.toArray()); - } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { + if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { clearSummaryForAirplaneMode(); } } }; - private void handleWifiApStateChanged(int state, int reason) { + @VisibleForTesting + void handleWifiApStateChanged(int state, int reason) { switch (state) { case WifiManager.WIFI_AP_STATE_ENABLING: mPreference.setSummary(R.string.wifi_tether_starting); break; case WifiManager.WIFI_AP_STATE_ENABLED: - /** - * Summary on enable is handled by tether - * broadcast notice - */ + WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration(); + updateConfigSummary(wifiConfig); break; case WifiManager.WIFI_AP_STATE_DISABLING: mPreference.setSummary(R.string.wifi_tether_stopping); @@ -208,32 +189,6 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController } } - private void updateTetherState(Object[] tethered, Object[] errored) { - boolean wifiTethered = matchRegex(tethered); - boolean wifiErrored = matchRegex(errored); - - if (wifiTethered) { - WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration(); - updateConfigSummary(wifiConfig); - } else if (wifiErrored) { - mPreference.setSummary(R.string.wifi_error); - } else { - mPreference.setSummary(R.string.wifi_hotspot_off_subtext); - } - } - - private boolean matchRegex(Object[] tethers) { - for (Object o : tethers) { - String s = (String) o; - for (String regex : mWifiRegexs) { - if (s.matches(regex)) { - return true; - } - } - } - return false; - } - private void updateConfigSummary(WifiConfiguration wifiConfig) { final String s = mContext.getString( com.android.internal.R.string.wifi_tether_configure_ssid_default); diff --git a/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java index 627bf32d215..699e5ae880b 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java @@ -21,6 +21,7 @@ import static android.net.ConnectivityManager.TETHERING_WIFI; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Handler; @@ -36,12 +37,19 @@ import com.android.settingslib.core.lifecycle.events.OnStop; public class WifiTetherSwitchBarController implements SwitchWidgetController.OnSwitchChangeListener, LifecycleObserver, OnStart, OnStop { + private static final IntentFilter WIFI_INTENT_FILTER; + private final Context mContext; private final SwitchWidgetController mSwitchBar; private final ConnectivityManager mConnectivityManager; private final DataSaverBackend mDataSaverBackend; private final WifiManager mWifiManager; + static { + WIFI_INTENT_FILTER = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); + WIFI_INTENT_FILTER.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + } + WifiTetherSwitchBarController(Context context, SwitchWidgetController switchBar) { mContext = context; mSwitchBar = switchBar; @@ -56,8 +64,7 @@ public class WifiTetherSwitchBarController implements SwitchWidgetController.OnS @Override public void onStart() { mSwitchBar.startListening(); - mContext.registerReceiver(mReceiver, - WifiTetherPreferenceController.WIFI_TETHER_INTENT_FILTER); + mContext.registerReceiver(mReceiver, WIFI_INTENT_FILTER); } @Override diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java index dca69748a95..4b18fcff273 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java @@ -73,7 +73,8 @@ import java.util.ArrayList; }) public class WifiTetherPreferenceControllerTest { - @Mock + private static final String SSID = "Pixel"; + private Context mContext; @Mock private ConnectivityManager mConnectivityManager; @@ -81,6 +82,8 @@ public class WifiTetherPreferenceControllerTest { private WifiManager mWifiManager; @Mock private PreferenceScreen mScreen; + @Mock + private WifiConfiguration mWifiConfiguration; private WifiTetherPreferenceController mController; private Lifecycle mLifecycle; @@ -90,6 +93,8 @@ public class WifiTetherPreferenceControllerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); mLifecycleOwner = () -> mLifecycle; mLifecycle = new Lifecycle(mLifecycleOwner); FakeFeatureFactory.setupForTest(); @@ -98,10 +103,13 @@ public class WifiTetherPreferenceControllerTest { .thenReturn(mConnectivityManager); when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager); when(mScreen.findPreference(anyString())).thenReturn(mPreference); + when(mWifiManager.getWifiApConfiguration()).thenReturn(mWifiConfiguration); + mWifiConfiguration.SSID = SSID; when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"}); mController = new WifiTetherPreferenceController(mContext, mLifecycle, false /* initSoftApManager */); + mController.displayPreference(mScreen); } @After @@ -127,7 +135,6 @@ public class WifiTetherPreferenceControllerTest { public void startAndStop_shouldRegisterUnregisterReceiver() { final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver"); - mController.displayPreference(mScreen); mLifecycle.handleLifecycleEvent(ON_START); mLifecycle.handleLifecycleEvent(ON_STOP); @@ -166,48 +173,6 @@ public class WifiTetherPreferenceControllerTest { verify(pref).setChecked(true); } - @Test - public void testReceiver_apStateChangedToDisabled_shouldUpdatePreferenceSummary() { - mController.displayPreference(mScreen); - receiveApStateChangedBroadcast(WifiManager.WIFI_AP_STATE_DISABLED); - assertThat(mPreference.getSummary().toString()).isEqualTo( - RuntimeEnvironment.application.getString(R.string.wifi_hotspot_off_subtext)); - } - - @Test - public void testReceiver_apStateChangedToDisabling_shouldUpdatePreferenceSummary() { - mController.displayPreference(mScreen); - receiveApStateChangedBroadcast(WifiManager.WIFI_AP_STATE_DISABLING); - assertThat(mPreference.getSummary().toString()).isEqualTo( - RuntimeEnvironment.application.getString(R.string.wifi_tether_stopping)); - } - - @Test - public void testReceiver_apStateChangedToEnabling_shouldUpdatePreferenceSummary() { - mController.displayPreference(mScreen); - receiveApStateChangedBroadcast(WifiManager.WIFI_AP_STATE_ENABLING); - assertThat(mPreference.getSummary().toString()).isEqualTo( - RuntimeEnvironment.application.getString(R.string.wifi_tether_starting)); - } - - @Test - public void testReceiver_apStateChangedToEnabled_shouldNotUpdatePreferenceSummary() { - mController.displayPreference(mScreen); - receiveApStateChangedBroadcast(WifiManager.WIFI_AP_STATE_DISABLED); - assertThat(mPreference.getSummary().toString()).isEqualTo( - RuntimeEnvironment.application.getString(R.string.wifi_hotspot_off_subtext)); - - // When turning on the hotspot, we receive STATE_ENABLING followed by STATE_ENABLED. The - // first should change the status to wifi_tether_starting, and the second should not change - // this. - receiveApStateChangedBroadcast(WifiManager.WIFI_AP_STATE_ENABLING); - assertThat(mPreference.getSummary().toString()).isEqualTo( - RuntimeEnvironment.application.getString(R.string.wifi_tether_starting)); - receiveApStateChangedBroadcast(WifiManager.WIFI_AP_STATE_ENABLED); - assertThat(mPreference.getSummary().toString()).isEqualTo( - RuntimeEnvironment.application.getString(R.string.wifi_tether_starting)); - } - @Test public void testReceiver_goingToAirplaneMode_shouldClearPreferenceSummary() { final ContentResolver cr = mock(ContentResolver.class); @@ -224,22 +189,32 @@ public class WifiTetherPreferenceControllerTest { } @Test - public void testReceiver_tetherEnabled_shouldUpdatePreferenceSummary() { - mController.displayPreference(mScreen); - final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver"); - final Intent broadcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); - final ArrayList activeTethers = new ArrayList<>(); - activeTethers.add("1"); - broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeTethers); - broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER, - new ArrayList<>()); - final WifiConfiguration configuration = new WifiConfiguration(); - configuration.SSID = "test-ap"; - when(mWifiManager.getWifiApConfiguration()).thenReturn(configuration); + public void testHandleWifiApStateChanged_stateEnabling_showEnablingSummary() { + mController.handleWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0 /* reason */); - receiver.onReceive(RuntimeEnvironment.application, broadcast); + assertThat(mPreference.getSummary()).isEqualTo("Turning hotspot on\u2026"); + } - verify(mContext).getString(eq(R.string.wifi_tether_enabled_subtext), any()); + @Test + public void testHandleWifiApStateChanged_stateEnabled_showEnabledSummary() { + mController.handleWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0 /* reason */); + + assertThat(mPreference.getSummary()).isEqualTo("Pixel is active"); + } + + @Test + public void testHandleWifiApStateChanged_stateDisabling_showDisablingSummary() { + mController.handleWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0 /* reason */); + + assertThat(mPreference.getSummary()).isEqualTo("Turning off hotspot\u2026"); + } + + @Test + public void testHandleWifiApStateChanged_stateDisabled_showDisabledSummary() { + mController.handleWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0 /* reason */); + + assertThat(mPreference.getSummary()).isEqualTo( + "Not sharing internet or content with other devices"); } @Implements(WifiTetherSettings.class) @@ -285,17 +260,4 @@ public class WifiTetherPreferenceControllerTest { onStopCalled = true; } } - - /** - * Helper to cause the controller to receive a WIFI_AP_STATE_CHANGED_ACTION with a specific - * state. - * - * @param state - the state, as specified by one of the WifiManager.WIFI_AP_STATE_* values - */ - private void receiveApStateChangedBroadcast(int state) { - final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver"); - final Intent broadcast = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - broadcast.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, state); - receiver.onReceive(RuntimeEnvironment.application, broadcast); - } } From 72489725c6dbcf69de4ee96ac0d67570e24e4e5a Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Thu, 16 Nov 2017 11:03:40 -0800 Subject: [PATCH 2/9] Change superclass to InstrumentedFragment. - for fragments that do not implement the preference screen, change them to inherit from InstrumentedFragment instead. Change-Id: I791c2634024bd2c248efea955be5c680180d735c Fixes: 68277111 Test: make RunSettingsRoboTests --- .../android/settings/CryptKeeperConfirm.java | 11 +++++++++-- src/com/android/settings/MasterClear.java | 4 ++-- .../android/settings/MasterClearConfirm.java | 4 ++-- src/com/android/settings/ProxySelector.java | 11 ++++------- src/com/android/settings/ResetNetwork.java | 4 ++-- .../android/settings/ResetNetworkConfirm.java | 4 ++-- .../settings/TrustedCredentialsSettings.java | 7 ++++--- .../applications/RunningServiceDetails.java | 4 ++-- .../FingerprintAuthenticateSidecar.java | 4 ++-- .../fingerprint/FingerprintRemoveSidecar.java | 18 ++---------------- .../UserDictionaryAddWordFragment.java | 5 ++--- .../settings/network/NetworkScorerPicker.java | 6 +++++- src/com/android/settings/nfc/AndroidBeam.java | 4 ++-- .../settings/password/ChooseLockPassword.java | 4 ++-- .../settings/password/ChooseLockPattern.java | 4 ++-- .../ConfirmDeviceCredentialBaseFragment.java | 4 ++-- .../settings/password/ConfirmLockPassword.java | 5 ----- .../settings/password/ConfirmLockPattern.java | 5 ----- .../settings/security/CryptKeeperSettings.java | 1 + 19 files changed, 47 insertions(+), 62 deletions(-) diff --git a/src/com/android/settings/CryptKeeperConfirm.java b/src/com/android/settings/CryptKeeperConfirm.java index d61fd983f3c..227120089ee 100644 --- a/src/com/android/settings/CryptKeeperConfirm.java +++ b/src/com/android/settings/CryptKeeperConfirm.java @@ -16,6 +16,7 @@ package com.android.settings; +import android.annotation.Nullable; import android.app.Activity; import android.app.StatusBarManager; import android.content.Context; @@ -35,11 +36,11 @@ import android.widget.Button; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import java.util.Locale; -public class CryptKeeperConfirm extends InstrumentedPreferenceFragment { +public class CryptKeeperConfirm extends InstrumentedFragment { private static final String TAG = "CryptKeeperConfirm"; @@ -153,6 +154,12 @@ public class CryptKeeperConfirm extends InstrumentedPreferenceFragment { mFinalButton.setOnClickListener(mFinalClickListener); } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().setTitle(R.string.crypt_keeper_confirm_title); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index 47aa8a6b9a9..4f5c6b95bfc 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -55,7 +55,7 @@ import android.widget.ScrollView; import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ConfirmLockPattern; import com.android.settingslib.RestrictedLockUtils; @@ -72,7 +72,7 @@ import java.util.List; * * This is the initial screen. */ -public class MasterClear extends InstrumentedPreferenceFragment { +public class MasterClear extends InstrumentedFragment { private static final String TAG = "MasterClear"; @VisibleForTesting static final int KEYGUARD_REQUEST = 55; diff --git a/src/com/android/settings/MasterClearConfirm.java b/src/com/android/settings/MasterClearConfirm.java index 9b324c3fddf..59736fd4cf5 100644 --- a/src/com/android/settings/MasterClearConfirm.java +++ b/src/com/android/settings/MasterClearConfirm.java @@ -33,7 +33,7 @@ import android.widget.Button; import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settingslib.RestrictedLockUtils; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -48,7 +48,7 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; * * This is the confirmation screen. */ -public class MasterClearConfirm extends InstrumentedPreferenceFragment { +public class MasterClearConfirm extends InstrumentedFragment { private View mContentView; private boolean mEraseSdCard; diff --git a/src/com/android/settings/ProxySelector.java b/src/com/android/settings/ProxySelector.java index a72525c2640..79767fb7aa3 100644 --- a/src/com/android/settings/ProxySelector.java +++ b/src/com/android/settings/ProxySelector.java @@ -41,9 +41,9 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; -public class ProxySelector extends InstrumentedPreferenceFragment implements DialogCreatable { +public class ProxySelector extends InstrumentedFragment implements DialogCreatable { private static final String TAG = "ProxySelector"; EditText mHostnameField; @@ -58,11 +58,6 @@ public class ProxySelector extends InstrumentedPreferenceFragment implements Dia private SettingsDialogFragment mDialogFragment; private View mView; - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -179,6 +174,8 @@ public class ProxySelector extends InstrumentedPreferenceFragment implements Dia String title = intent.getStringExtra("title"); if (!TextUtils.isEmpty(title)) { activity.setTitle(title); + } else { + activity.setTitle(R.string.proxy_settings_title); } } diff --git a/src/com/android/settings/ResetNetwork.java b/src/com/android/settings/ResetNetwork.java index f64f6dce93a..5cbee6385df 100644 --- a/src/com/android/settings/ResetNetwork.java +++ b/src/com/android/settings/ResetNetwork.java @@ -43,7 +43,7 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.PhoneConstants; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ConfirmLockPattern; import com.android.settingslib.RestrictedLockUtils; @@ -61,7 +61,7 @@ import java.util.List; * * This is the initial screen. */ -public class ResetNetwork extends InstrumentedPreferenceFragment { +public class ResetNetwork extends InstrumentedFragment { private static final String TAG = "ResetNetwork"; // Arbitrary to avoid conficts diff --git a/src/com/android/settings/ResetNetworkConfirm.java b/src/com/android/settings/ResetNetworkConfirm.java index bc0fa774a75..78e83394101 100644 --- a/src/com/android/settings/ResetNetworkConfirm.java +++ b/src/com/android/settings/ResetNetworkConfirm.java @@ -42,8 +42,8 @@ import android.widget.Toast; import com.android.ims.ImsManager; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.PhoneConstants; -import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.wrapper.RecoverySystemWrapper; +import com.android.settings.core.InstrumentedFragment; import com.android.settingslib.RestrictedLockUtils; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -58,7 +58,7 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; * * This is the confirmation screen. */ -public class ResetNetworkConfirm extends InstrumentedPreferenceFragment { +public class ResetNetworkConfirm extends InstrumentedFragment { private View mContentView; private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; diff --git a/src/com/android/settings/TrustedCredentialsSettings.java b/src/com/android/settings/TrustedCredentialsSettings.java index 491419a7212..86340be98f6 100644 --- a/src/com/android/settings/TrustedCredentialsSettings.java +++ b/src/com/android/settings/TrustedCredentialsSettings.java @@ -65,7 +65,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; @@ -75,7 +75,7 @@ import java.util.List; import java.util.Set; import java.util.function.IntConsumer; -public class TrustedCredentialsSettings extends InstrumentedPreferenceFragment +public class TrustedCredentialsSettings extends InstrumentedFragment implements TrustedCredentialsDialogBuilder.DelegateInterface { public static final String ARG_SHOW_NEW_FOR_USER = "ARG_SHOW_NEW_FOR_USER"; @@ -117,7 +117,8 @@ public class TrustedCredentialsSettings extends InstrumentedPreferenceFragment private final int mContentView; private final boolean mSwitch; - private Tab(String tag, int label, int view, int progress, int contentView, boolean withSwitch) { + private Tab(String tag, int label, int view, int progress, int contentView, + boolean withSwitch) { mTag = tag; mLabel = label; mView = view; diff --git a/src/com/android/settings/applications/RunningServiceDetails.java b/src/com/android/settings/applications/RunningServiceDetails.java index 7e73a9b5001..770b1d6e4a8 100644 --- a/src/com/android/settings/applications/RunningServiceDetails.java +++ b/src/com/android/settings/applications/RunningServiceDetails.java @@ -34,7 +34,7 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.Utils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settingslib.utils.ThreadUtils; @@ -45,7 +45,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -public class RunningServiceDetails extends InstrumentedPreferenceFragment +public class RunningServiceDetails extends InstrumentedFragment implements RunningState.OnRefreshUiListener { static final String TAG = "RunningServicesDetails"; diff --git a/src/com/android/settings/fingerprint/FingerprintAuthenticateSidecar.java b/src/com/android/settings/fingerprint/FingerprintAuthenticateSidecar.java index d649c0bfc7f..1fa59a2fb60 100644 --- a/src/com/android/settings/fingerprint/FingerprintAuthenticateSidecar.java +++ b/src/com/android/settings/fingerprint/FingerprintAuthenticateSidecar.java @@ -21,12 +21,12 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.os.CancellationSignal; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; /** * Sidecar fragment to handle the state around fingerprint authentication */ -public class FingerprintAuthenticateSidecar extends InstrumentedPreferenceFragment { +public class FingerprintAuthenticateSidecar extends InstrumentedFragment { private static final String TAG = "FingerprintAuthenticateSidecar"; diff --git a/src/com/android/settings/fingerprint/FingerprintRemoveSidecar.java b/src/com/android/settings/fingerprint/FingerprintRemoveSidecar.java index 462d09ee9e1..7caca3fff57 100644 --- a/src/com/android/settings/fingerprint/FingerprintRemoveSidecar.java +++ b/src/com/android/settings/fingerprint/FingerprintRemoveSidecar.java @@ -21,7 +21,7 @@ import android.content.Context; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import android.os.UserHandle; import java.util.Queue; @@ -31,7 +31,7 @@ import android.util.Log; /** * Sidecar fragment to handle the state around fingerprint removal. */ -public class FingerprintRemoveSidecar extends InstrumentedPreferenceFragment { +public class FingerprintRemoveSidecar extends InstrumentedFragment { private static final String TAG = "FingerprintRemoveSidecar"; private Listener mListener; @@ -99,20 +99,6 @@ public class FingerprintRemoveSidecar extends InstrumentedPreferenceFragment { setRetainInstance(true); } - @Override - public void onAttach(Context context) { - super.onAttach(context); - } - - @Override - public void onResume() { - super.onResume(); - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - } - public void setListener(Listener listener) { if (mListener == null && listener != null) { while (!mFingerprintsRemoved.isEmpty()) { diff --git a/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java b/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java index 38b64a598db..3243e56c2f6 100644 --- a/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java +++ b/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java @@ -27,7 +27,7 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.inputmethod.UserDictionaryAddWordContents.LocaleRenderer; @@ -41,7 +41,7 @@ import java.util.Locale; * As opposed to the UserDictionaryActivity, this is only invoked within Settings * from the UserDictionarySettings. */ -public class UserDictionaryAddWordFragment extends InstrumentedPreferenceFragment +public class UserDictionaryAddWordFragment extends InstrumentedFragment implements AdapterView.OnItemSelectedListener, com.android.internal.app.LocalePicker.LocaleSelectionListener { @@ -55,7 +55,6 @@ public class UserDictionaryAddWordFragment extends InstrumentedPreferenceFragmen public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); - getActivity().getActionBar().setTitle(R.string.user_dict_settings_title); // Keep the instance so that we remember mContents when configuration changes (eg rotation) setRetainInstance(true); } diff --git a/src/com/android/settings/network/NetworkScorerPicker.java b/src/com/android/settings/network/NetworkScorerPicker.java index 187c9ce3ebc..34accf24671 100644 --- a/src/com/android/settings/network/NetworkScorerPicker.java +++ b/src/com/android/settings/network/NetworkScorerPicker.java @@ -50,7 +50,6 @@ public class NetworkScorerPicker extends InstrumentedPreferenceFragment implemen @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); - addPreferencesFromResource(R.xml.network_scorer_picker_prefs); updateCandidates(); } @@ -69,6 +68,11 @@ public class NetworkScorerPicker extends InstrumentedPreferenceFragment implemen return view; } + @Override + protected int getPreferenceScreenResId() { + return R.xml.network_scorer_picker_prefs; + } + @VisibleForTesting public void updateCandidates() { final PreferenceScreen screen = getPreferenceScreen(); diff --git a/src/com/android/settings/nfc/AndroidBeam.java b/src/com/android/settings/nfc/AndroidBeam.java index 707017beaf4..8377f143f08 100644 --- a/src/com/android/settings/nfc/AndroidBeam.java +++ b/src/com/android/settings/nfc/AndroidBeam.java @@ -29,7 +29,7 @@ import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.HelpUtils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.ShowAdminSupportDetailsDialog; @@ -38,7 +38,7 @@ import com.android.settingslib.RestrictedLockUtils; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -public class AndroidBeam extends InstrumentedPreferenceFragment +public class AndroidBeam extends InstrumentedFragment implements SwitchBar.OnSwitchChangeListener { private View mView; private NfcAdapter mNfcAdapter; diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 11efec3ca7f..a9c5c032769 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -64,7 +64,7 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.SetupWizardUtils; import com.android.settings.Utils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.notification.RedactionInterstitial; import com.android.settings.widget.ImeAwareEditText; import com.android.setupwizardlib.GlifLayout; @@ -168,7 +168,7 @@ public class ChooseLockPassword extends SettingsActivity { layout.setFitsSystemWindows(false); } - public static class ChooseLockPasswordFragment extends InstrumentedPreferenceFragment + public static class ChooseLockPasswordFragment extends InstrumentedFragment implements OnClickListener, OnEditorActionListener, TextWatcher, SaveAndFinishWorker.Listener { private static final String KEY_FIRST_PIN = "first_pin"; diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index 972fac8715f..0df1a11c9f2 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -43,7 +43,7 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.SetupWizardUtils; import com.android.settings.Utils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.notification.RedactionInterstitial; import com.android.setupwizardlib.GlifLayout; @@ -153,7 +153,7 @@ public class ChooseLockPattern extends SettingsActivity { return super.onKeyDown(keyCode, event); } - public static class ChooseLockPatternFragment extends InstrumentedPreferenceFragment + public static class ChooseLockPatternFragment extends InstrumentedFragment implements View.OnClickListener, SaveAndFinishWorker.Listener { public static final int CONFIRM_EXISTING_REQUEST = 55; diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java index 5b18925f1c7..3f2675029d0 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java @@ -52,13 +52,13 @@ import android.widget.TextView; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.Utils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; import com.android.settings.fingerprint.FingerprintUiHelper; /** * Base fragment to be shared for PIN/Pattern/Password confirmation fragments. */ -public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedPreferenceFragment +public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment implements FingerprintUiHelper.Callback { public static final String PACKAGE = "com.android.settings"; diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java index 20182cbd92a..dbca2fc12d0 100644 --- a/src/com/android/settings/password/ConfirmLockPassword.java +++ b/src/com/android/settings/password/ConfirmLockPassword.java @@ -113,11 +113,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java index 1e881505027..c87ff431e8f 100644 --- a/src/com/android/settings/password/ConfirmLockPattern.java +++ b/src/com/android/settings/password/ConfirmLockPattern.java @@ -104,11 +104,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/src/com/android/settings/security/CryptKeeperSettings.java b/src/com/android/settings/security/CryptKeeperSettings.java index 7d5ee9d4357..64f5abb5bc5 100644 --- a/src/com/android/settings/security/CryptKeeperSettings.java +++ b/src/com/android/settings/security/CryptKeeperSettings.java @@ -157,6 +157,7 @@ public class CryptKeeperSettings extends InstrumentedPreferenceFragment { } } } + activity.setTitle(R.string.crypt_keeper_encrypt_title); } /** From c21cfe9ea21d72a658c68954b3ddc99d7fff9cab Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Sun, 4 Feb 2018 21:22:00 -0800 Subject: [PATCH 3/9] Autofill compat - Settings Test: manully can change autofill serivces Update settings due to API signature change. Change-Id: Idaf00fdbe7cb07cb174f3bbd8edcfa95ac734764 --- .../defaultapps/DefaultAutofillPicker.java | 10 +++++----- .../DefaultAutofillPreferenceController.java | 2 +- .../DefaultAutofillPreferenceControllerTest.java | 5 ----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java index 97f6d4bec5e..d116f91d601 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java @@ -251,16 +251,16 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { static final class AutofillSettingIntentProvider implements SettingIntentProvider { private final String mSelectedKey; - private final PackageManager mPackageManager; + private final Context mContext; - public AutofillSettingIntentProvider(PackageManager packageManager, String key) { + public AutofillSettingIntentProvider(Context context, String key) { mSelectedKey = key; - mPackageManager = packageManager; + mContext = context; } @Override public Intent getIntent() { - final List resolveInfos = mPackageManager.queryIntentServices( + final List resolveInfos = mContext.getPackageManager().queryIntentServices( AUTOFILL_PROBE, PackageManager.GET_META_DATA); for (ResolveInfo resolveInfo : resolveInfos) { @@ -270,7 +270,7 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { if (TextUtils.equals(mSelectedKey, flattenKey)) { final String settingsActivity; try { - settingsActivity = new AutofillServiceInfo(mPackageManager, serviceInfo) + settingsActivity = new AutofillServiceInfo(mContext, serviceInfo) .getSettingsActivity(); } catch (SecurityException e) { // Service does not declare the proper permission, ignore it. diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java index 508cc63cfc3..b159d1dbd8c 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java @@ -53,7 +53,7 @@ public class DefaultAutofillPreferenceController extends DefaultAppPreferenceCon } final DefaultAutofillPicker.AutofillSettingIntentProvider intentProvider = new DefaultAutofillPicker.AutofillSettingIntentProvider( - mPackageManager.getPackageManager(), info.getKey()); + mContext, info.getKey()); return intentProvider.getIntent(); } diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceControllerTest.java index bc72ee4ee3f..4e8a79efa9e 100644 --- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceControllerTest.java @@ -104,10 +104,5 @@ public class DefaultAutofillPreferenceControllerTest { final DefaultAppInfo info = mController.getDefaultAppInfo(); assertThat(info).isNotNull(); - - mController.getSettingIntent(info); - - verify(mPackageManager.getPackageManager()).queryIntentServices( - DefaultAutofillPicker.AUTOFILL_PROBE, PackageManager.GET_META_DATA); } } From 900afbbc65f0935d1d36d5de413b703366fd2702 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Mon, 5 Feb 2018 12:38:15 -0800 Subject: [PATCH 4/9] Update string for wifi calling. Bug: 72330968 Test: rebuild Change-Id: Ie1e189c1339762d4763540e1e678357020602f35 --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 4b2c67344dc..49d950d9e0b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2259,9 +2259,9 @@ Wi-Fi calling - Turn on Wi-Fi Calling + Extend call coverage with Wi\u2011Fi - Extend coverage by calling over Wi-Fi + Turn on Wi\u2011Fi calling Calling preference From 8fe5d507d5c6828f3d33ab5af650051ca2ef6d3a Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 30 Jan 2018 18:10:38 -0800 Subject: [PATCH 5/9] Increase app icon size from 24dp to 32dp. The app icon size as 24dp is too small. We'd like to increase to 32dp based on latest UX feedback and to match Material Spec v2 wrt app icon sizes. Test: make RunSettingsRoboTest -j40 ROBOTEST_FILTER=AppPreferenceTest Test: visual (Launch settings -> App & notification -> verify app icon size increased) Fixes: 71767701 Change-Id: I474df72075602fc48d3123127b4f9bf350374506 --- res/values/dimens.xml | 2 +- .../com/android/settings/widget/AppPreferenceTest.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 12193c43b34..77a95b08cc9 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -22,7 +22,7 @@ 16dip 40dip - 24dp + 32dp 48dp 64dip 72dip diff --git a/tests/robotests/src/com/android/settings/widget/AppPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/AppPreferenceTest.java index d4890942c6d..6556f1c7136 100644 --- a/tests/robotests/src/com/android/settings/widget/AppPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/widget/AppPreferenceTest.java @@ -36,6 +36,8 @@ import org.robolectric.annotation.Config; @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class AppPreferenceTest { + private static final int EXPECTED_APP_ICON_SIZE_DP = 32; + private Context mContext; private View mRootView; private AppPreference mPref; @@ -75,4 +77,12 @@ public class AppPreferenceTest { assertThat(mHolder.findViewById(R.id.summary_container).getVisibility()) .isEqualTo(View.GONE); } + + @Test + public void foobar_testName() { + // Can't use isEquals() to compare float. Use isWithIn().of() instead. + assertThat(mContext.getResources().getDimension(R.dimen.secondary_app_icon_size)) + .isWithin(0.01f) + .of(EXPECTED_APP_ICON_SIZE_DP); + } } From 845e213514a6170e02cbb5395d1d077b2f25f635 Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Mon, 5 Feb 2018 15:58:58 -0800 Subject: [PATCH 6/9] Set categories to the adapter if suggestion is not available. If there's no suggestion, we should set the category to the adapter before returning. Bug: none Test: robotests Change-Id: I73bb248d17edb3c398a9fb0a8f3913e7233fcc0b --- .../settings/dashboard/DashboardSummary.java | 1 + .../dashboard/DashboardSummaryTest.java | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index 4f045a28061..690c79559af 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -268,6 +268,7 @@ public class DashboardSummary extends InstrumentedFragment mSummaryLoader.updateSummaryToCache(category); mStagingCategory = category; if (mSuggestionControllerMixin == null) { + mAdapter.setCategory(mStagingCategory); return; } if (mSuggestionControllerMixin.isSuggestionLoaded()) { diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java index a1c8d67e034..d9c709e72e9 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java @@ -67,6 +67,8 @@ public class DashboardSummaryTest { private ConditionManager mConditionManager; @Mock private SummaryLoader mSummaryLoader; + @Mock + private SuggestionControllerMixin mSuggestionControllerMixin; private Context mContext; private DashboardSummary mSummary; @@ -111,12 +113,31 @@ public class DashboardSummaryTest { @Test public void updateCategory_shouldGetCategoryFromFeatureProvider() { + ReflectionHelpers.setField(mSummary, "mSuggestionControllerMixin", + mSuggestionControllerMixin); + + when(mSuggestionControllerMixin.isSuggestionLoaded()).thenReturn(true); doReturn(mock(Activity.class)).when(mSummary).getActivity(); mSummary.onAttach(mContext); mSummary.updateCategory(); verify(mSummaryLoader).updateSummaryToCache(nullable(DashboardCategory.class)); verify(mDashboardFeatureProvider).getTilesForCategory(CategoryKey.CATEGORY_HOMEPAGE); + verify(mAdapter).setCategory(any()); + } + + @Test + public void updateCategory_shouldGetCategoryFromFeatureProvider_evenIfSuggestionDisabled() { + when(mFeatureFactory.suggestionsFeatureProvider.isSuggestionEnabled(any(Context.class))) + .thenReturn(false); + + doReturn(mock(Activity.class)).when(mSummary).getActivity(); + mSummary.onAttach(mContext); + mSummary.updateCategory(); + + verify(mSummaryLoader).updateSummaryToCache(nullable(DashboardCategory.class)); + verify(mDashboardFeatureProvider).getTilesForCategory(CategoryKey.CATEGORY_HOMEPAGE); + verify(mAdapter).setCategory(any()); } @Test From ca80da20b11d10f5ca32fe3c602b51d673267f13 Mon Sep 17 00:00:00 2001 From: Maggie Date: Mon, 29 Jan 2018 20:29:16 -0800 Subject: [PATCH 7/9] Add a footer to Location Settings page At the bottom of Settings -> Security & Location -> Location, inject footers with text provided by system apps. GmsCore uses this footer to present legal consent info for Google Location Services. Bug: 70350519 Test: Robo Test: Manual Change-Id: I201afdb30baa7b81b591d161fce5be55569b6320 --- res/xml/location_settings.xml | 6 +- .../LocationFooterPreferenceController.java | 223 ++++++++++++++++++ .../settings/location/LocationSettings.java | 3 +- ...ocationFooterPreferenceControllerTest.java | 220 +++++++++++++++++ 4 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/location/LocationFooterPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/location/LocationFooterPreferenceControllerTest.java diff --git a/res/xml/location_settings.xml b/res/xml/location_settings.xml index 267fce98114..c86df68b2fc 100644 --- a/res/xml/location_settings.xml +++ b/res/xml/location_settings.xml @@ -49,5 +49,9 @@ + android:title="@string/location_category_location_services"/> + + diff --git a/src/com/android/settings/location/LocationFooterPreferenceController.java b/src/com/android/settings/location/LocationFooterPreferenceController.java new file mode 100644 index 00000000000..f15d43748de --- /dev/null +++ b/src/com/android/settings/location/LocationFooterPreferenceController.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2018 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.location; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.location.LocationManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.util.Log; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.widget.FooterPreference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Preference controller for location footer preference category + */ +public class LocationFooterPreferenceController extends LocationBasePreferenceController + implements LifecycleObserver, OnPause { + private static final String TAG = "LocationFooter"; + private static final String KEY_LOCATION_FOOTER = "location_footer"; + private static final Intent INJECT_INTENT = + new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION); + private final Context mContext; + private final PackageManager mPackageManager; + private Collection mFooterInjectors; + + public LocationFooterPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + mContext = context; + mPackageManager = mContext.getPackageManager(); + mFooterInjectors = new ArrayList<>(); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public String getPreferenceKey() { + return KEY_LOCATION_FOOTER; + } + + /** + * Insert footer preferences. Send a {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} + * broadcast to receivers who have injected a footer + */ + @Override + public void updateState(Preference preference) { + PreferenceCategory category = (PreferenceCategory) preference; + category.removeAll(); + mFooterInjectors.clear(); + Collection footerData = getFooterData(); + for (FooterData data : footerData) { + // Generate a footer preference with the given text + FooterPreference footerPreference = new FooterPreference(preference.getContext()); + String footerString; + try { + footerString = + mPackageManager + .getResourcesForApplication(data.applicationInfo) + .getString(data.footerStringRes); + } catch (NameNotFoundException exception) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w( + TAG, + "Resources not found for application " + + data.applicationInfo.packageName); + } + continue; + } + footerPreference.setTitle(footerString); + // Inject the footer + category.addPreference(footerPreference); + // Send broadcast to the injector announcing a footer has been injected + sendBroadcastFooterDisplayed(data.componentName); + mFooterInjectors.add(data.componentName); + } + } + + /** + * Do nothing on location mode changes. + */ + @Override + public void onLocationModeChanged(int mode, boolean restricted) {} + + /** + * Location footer preference group should be displayed if there is at least one footer to + * inject. + */ + @Override + public boolean isAvailable() { + return !getFooterData().isEmpty(); + } + + /** + * Send a {@link LocationManager#SETTINGS_FOOTER_REMOVED_ACTION} broadcast to footer injectors + * when LocationFragment is on pause + */ + @Override + public void onPause() { + // Send broadcast to the footer injectors. Notify them the footer is not visible. + for (ComponentName componentName : mFooterInjectors) { + final Intent intent = new Intent(LocationManager.SETTINGS_FOOTER_REMOVED_ACTION); + intent.setComponent(componentName); + mContext.sendBroadcast(intent); + } + } + + /** + * Send a {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast to a footer + * injector. + */ + @VisibleForTesting + void sendBroadcastFooterDisplayed(ComponentName componentName) { + Intent intent = new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION); + intent.setComponent(componentName); + mContext.sendBroadcast(intent); + } + + /** + * Return a list of strings with text provided by ACTION_INJECT_FOOTER broadcast receivers. + */ + private Collection getFooterData() { + // Fetch footer text from system apps + final List resolveInfos = + mPackageManager.queryBroadcastReceivers( + INJECT_INTENT, PackageManager.GET_META_DATA); + if (resolveInfos == null) { + if (Log.isLoggable(TAG, Log.ERROR)) { + Log.e(TAG, "Unable to resolve intent " + INJECT_INTENT); + return Collections.emptyList(); + } + } else if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Found broadcast receivers: " + resolveInfos); + } + + final Collection footerDataList = new ArrayList<>(resolveInfos.size()); + for (ResolveInfo resolveInfo : resolveInfos) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + final ApplicationInfo appInfo = activityInfo.applicationInfo; + + // If a non-system app tries to inject footer, ignore it + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Ignoring attempt to inject footer from app not in system image: " + + resolveInfo); + continue; + } + } + + // Get the footer text resource id from broadcast receiver's metadata + if (activityInfo.metaData == null) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "No METADATA in broadcast receiver " + activityInfo.name); + continue; + } + } + + final int footerTextRes = + activityInfo.metaData.getInt(LocationManager.METADATA_SETTINGS_FOOTER_STRING); + if (footerTextRes == 0) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w( + TAG, + "No mapping of integer exists for " + + LocationManager.METADATA_SETTINGS_FOOTER_STRING); + } + continue; + } + footerDataList.add( + new FooterData( + footerTextRes, + appInfo, + new ComponentName(activityInfo.packageName, activityInfo.name))); + } + return footerDataList; + } + + /** + * Contains information related to a footer. + */ + private static class FooterData { + + // The string resource of the footer + final int footerStringRes; + + // Application info of receiver injecting this footer + final ApplicationInfo applicationInfo; + + // The component that injected the footer. It must be a receiver of broadcast + // LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION + final ComponentName componentName; + + FooterData(int footerRes, ApplicationInfo appInfo, ComponentName componentName) { + this.footerStringRes = footerRes; + this.applicationInfo = appInfo; + this.componentName = componentName; + } + } +} diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 3cc5b847f15..510c1625a58 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -131,9 +131,10 @@ public class LocationSettings extends DashboardFragment { controllers.add(new LocationForWorkPreferenceController(context, lifecycle)); controllers.add( new RecentLocationRequestPreferenceController(context, fragment, lifecycle)); + controllers.add(new LocationScanningPreferenceController(context)); controllers.add( new LocationServicePreferenceController(context, fragment, lifecycle)); - controllers.add(new LocationScanningPreferenceController(context)); + controllers.add(new LocationFooterPreferenceController(context, lifecycle)); return controllers; } diff --git a/tests/robotests/src/com/android/settings/location/LocationFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationFooterPreferenceControllerTest.java new file mode 100644 index 00000000000..da00010ee53 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationFooterPreferenceControllerTest.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2018 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.location; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.arch.lifecycle.LifecycleOwner; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.location.LocationManager; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +/** Unit tests for {@link LocationFooterPreferenceController} */ +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class LocationFooterPreferenceControllerTest { + + @Mock + private PreferenceCategory mPreferenceCategory; + @Mock + private PackageManager mPackageManager; + @Mock + private Resources mResources; + private Context mContext; + private LocationFooterPreferenceController mController; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; + private static final int TEST_RES_ID = 1234; + private static final String TEST_TEXT = "text"; + + @Before + public void setUp() throws NameNotFoundException { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + when(mPreferenceCategory.getContext()).thenReturn(mContext); + mController = spy(new LocationFooterPreferenceController(mContext, mLifecycle)); + when(mPackageManager.getResourcesForApplication(any(ApplicationInfo.class))) + .thenReturn(mResources); + when(mResources.getString(TEST_RES_ID)).thenReturn(TEST_TEXT); + doNothing().when(mPreferenceCategory).removeAll(); + } + + @Test + public void isAvailable_hasValidFooter_returnsTrue() throws NameNotFoundException { + final List testResolveInfos = new ArrayList<>(); + testResolveInfos.add( + getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true)); + when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) + .thenReturn(testResolveInfos); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_noSystemApp_returnsFalse() throws NameNotFoundException { + final List testResolveInfos = new ArrayList<>(); + testResolveInfos.add( + getTestResolveInfo(/*isSystemApp*/ false, /*hasRequiredMetadata*/ true)); + when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) + .thenReturn(testResolveInfos); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_noRequiredMetadata_returnsFalse() throws NameNotFoundException { + final List testResolveInfos = new ArrayList<>(); + testResolveInfos.add( + getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ false)); + when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) + .thenReturn(testResolveInfos); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void sendBroadcastFooterInject() { + ArgumentCaptor intent = ArgumentCaptor.forClass(Intent.class); + final ActivityInfo activityInfo = + getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true).activityInfo; + mController.sendBroadcastFooterDisplayed( + new ComponentName(activityInfo.packageName, activityInfo.name)); + verify(mContext).sendBroadcast(intent.capture()); + assertThat(intent.getValue().getAction()) + .isEqualTo(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION); + } + + @Test + public void updateState_sendBroadcast() throws NameNotFoundException { + final List testResolveInfos = new ArrayList<>(); + testResolveInfos.add( + getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true)); + when(mPackageManager.queryBroadcastReceivers(any(), anyInt())) + .thenReturn(testResolveInfos); + mController.updateState(mPreferenceCategory); + ArgumentCaptor intent = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendBroadcast(intent.capture()); + assertThat(intent.getValue().getAction()) + .isEqualTo(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION); + } + + @Test + public void updateState_addPreferences() throws NameNotFoundException { + final List testResolveInfos = new ArrayList<>(); + testResolveInfos.add( + getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true)); + when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) + .thenReturn(testResolveInfos); + mController.updateState(mPreferenceCategory); + ArgumentCaptor pref = ArgumentCaptor.forClass(Preference.class); + verify(mPreferenceCategory).addPreference(pref.capture()); + assertThat(pref.getValue().getTitle()).isEqualTo(TEST_TEXT); + } + + @Test + public void updateState_notSystemApp_ignore() throws NameNotFoundException { + final List testResolveInfos = new ArrayList<>(); + testResolveInfos.add( + getTestResolveInfo(/*isSystemApp*/ false, /*hasRequiredMetadata*/ true)); + when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) + .thenReturn(testResolveInfos); + mController.updateState(mPreferenceCategory); + verify(mPreferenceCategory, never()).addPreference(any(Preference.class)); + verify(mContext, never()).sendBroadcast(any(Intent.class)); + } + + @Test + public void updateState_thenOnPause_sendBroadcasts() throws NameNotFoundException { + final List testResolveInfos = new ArrayList<>(); + testResolveInfos.add( + getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true)); + when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt())) + .thenReturn(testResolveInfos); + mController.updateState(mPreferenceCategory); + ArgumentCaptor intent = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendBroadcast(intent.capture()); + assertThat(intent.getValue().getAction()) + .isEqualTo(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION); + + mController.onPause(); + verify(mContext, times(2)).sendBroadcast(intent.capture()); + assertThat(intent.getValue().getAction()) + .isEqualTo(LocationManager.SETTINGS_FOOTER_REMOVED_ACTION); + } + + @Test + public void onPause_doNotSendBroadcast() { + mController.onPause(); + verify(mContext, never()).sendBroadcast(any(Intent.class)); + } + + /** + * Returns a ResolveInfo object for testing + * @param isSystemApp If true, the application is a system app. + * @param hasRequiredMetaData If true, the broadcast receiver has a valid value for + * {@link LocationManager#METADATA_SETTINGS_FOOTER_STRING} + */ + private ResolveInfo getTestResolveInfo(boolean isSystemApp, boolean hasRequiredMetaData) { + ResolveInfo testResolveInfo = new ResolveInfo(); + ApplicationInfo testAppInfo = new ApplicationInfo(); + if (isSystemApp) { + testAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + } + ActivityInfo testActivityInfo = new ActivityInfo(); + testActivityInfo.name = "TestActivityName"; + testActivityInfo.packageName = "TestPackageName"; + testActivityInfo.applicationInfo = testAppInfo; + if (hasRequiredMetaData) { + testActivityInfo.metaData = new Bundle(); + testActivityInfo.metaData.putInt( + LocationManager.METADATA_SETTINGS_FOOTER_STRING, TEST_RES_ID); + } + testResolveInfo.activityInfo = testActivityInfo; + return testResolveInfo; + } +} From ab0cde6bad5005e5c7fba3e9e17ca6b6408a09ff Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Fri, 12 Jan 2018 13:52:20 -0800 Subject: [PATCH 8/9] Add app restrict tip and detector 1. Add RestrictAppTip with two state(new and handled) 2. Add RestrictAppDetector, and future cl will hook up it with anomaly database 3. Add related dialog in BatteryTipDialogFragment Bug: 72385333 Test: RunSettingsRoboTests Change-Id: Ic10efc6387150e62b6c6ad8d4c0d16ff75564fac --- res/values/strings.xml | 22 ++++ .../batterytip/BatteryTipDialogFragment.java | 20 ++++ .../batterytip/BatteryTipLoader.java | 2 + .../batterytip/HighUsageAdapter.java | 4 +- .../detectors/RestrictAppDetector.java | 46 ++++++++ .../batterytip/tips/RestrictAppTip.java | 99 ++++++++++++++++ .../BatteryTipDialogFragmentTest.java | 18 +++ .../batterytip/BatteryTipLoaderTest.java | 1 + .../batterytip/tips/RestrictAppTipTest.java | 111 ++++++++++++++++++ 9 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/fuelgauge/batterytip/detectors/RestrictAppDetector.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 4b2c67344dc..1c58a90ea95 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4842,6 +4842,28 @@ Your tablet was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your tablet was used for about %1$s since last full charge.\n\n Total usage: Your device was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your device was used for about %1$s since last full charge.\n\n Total usage: + + + Restrict %1$d app + Restrict %1$d apps + + + + %1$d recently restricted + %1$d apps recently restricted + + + + %1$s has high battery usage + %2$d apps have high battery usage + + + App changes are in progress + + + To save battery, you can stop this app from running in the background when it’s not being used. + + Restrict Smart battery manager diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java index b51474defaa..6d9aaabb810 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java @@ -34,6 +34,9 @@ import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController. import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; + +import java.util.List; /** * Dialog Fragment to show action dialog for each anomaly @@ -84,6 +87,23 @@ public class BatteryTipDialogFragment extends InstrumentedDialogFragment impleme .setView(view) .setPositiveButton(android.R.string.ok, null) .create(); + case BatteryTip.TipType.APP_RESTRICTION: + final RestrictAppTip restrictAppTip = (RestrictAppTip) mBatteryTip; + final RecyclerView restrictionView = (RecyclerView) LayoutInflater.from( + context).inflate(R.layout.recycler_view, null); + final List restrictedAppList = restrictAppTip.getRestrictAppList(); + final int num = restrictedAppList.size(); + restrictionView.setLayoutManager(new LinearLayoutManager(context)); + restrictionView.setAdapter(new HighUsageAdapter(context, restrictedAppList)); + + return new AlertDialog.Builder(context) + .setTitle(context.getResources().getQuantityString( + R.plurals.battery_tip_restrict_title, num, num)) + .setMessage(getString(R.string.battery_tip_restrict_app_dialog_message)) + .setView(restrictionView) + .setPositiveButton(R.string.battery_tip_restrict_app_dialog_ok, this) + .setNegativeButton(android.R.string.cancel, null) + .create(); default: throw new IllegalArgumentException("unknown type " + mBatteryTip.getType()); } diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java index ced34616879..a61584168bc 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java @@ -26,6 +26,7 @@ import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector; import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector; import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector; import com.android.settings.fuelgauge.batterytip.detectors.SmartBatteryDetector; +import com.android.settings.fuelgauge.batterytip.detectors.RestrictAppDetector; import com.android.settings.fuelgauge.batterytip.detectors.SummaryDetector; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip; @@ -70,6 +71,7 @@ public class BatteryTipLoader extends AsyncLoader> { tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect()); tips.add(new EarlyWarningDetector(policy, context).detect()); tips.add(new SummaryDetector(policy).detect()); + tips.add(new RestrictAppDetector(policy).detect()); Collections.sort(tips); return tips; diff --git a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java index 60aa6c8832a..6c129d8a9be 100644 --- a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java +++ b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java @@ -77,7 +77,9 @@ public class HighUsageAdapter extends RecyclerView.Adapter highUsageApps = new ArrayList<>(); + return new RestrictAppTip( + highUsageApps.isEmpty() ? BatteryTip.StateType.INVISIBLE : BatteryTip.StateType.NEW, + highUsageApps); + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java new file mode 100644 index 00000000000..054b6e19421 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.batterytip.tips; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Parcel; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.fuelgauge.batterytip.AppInfo; + +import java.util.List; + +/** + * Tip to suggest user to restrict some bad apps + */ +public class RestrictAppTip extends BatteryTip { + private List mRestrictAppList; + + public RestrictAppTip(@StateType int state, List highUsageApps) { + super(TipType.APP_RESTRICTION, state, true /* showDialog */); + mRestrictAppList = highUsageApps; + } + + @VisibleForTesting + RestrictAppTip(Parcel in) { + super(in); + mRestrictAppList = in.createTypedArrayList(AppInfo.CREATOR); + } + + @Override + public CharSequence getTitle(Context context) { + final int num = mRestrictAppList.size(); + return context.getResources().getQuantityString( + mState == StateType.HANDLED + ? R.plurals.battery_tip_restrict_handled_title + : R.plurals.battery_tip_restrict_title, + num, num); + } + + @Override + public CharSequence getSummary(Context context) { + final int num = mRestrictAppList.size(); + final CharSequence appLabel = num > 0 ? Utils.getApplicationLabel(context, + mRestrictAppList.get(0).packageName) : ""; + return mState == StateType.HANDLED + ? context.getString(R.string.battery_tip_restrict_handled_summary) + : context.getResources().getQuantityString(R.plurals.battery_tip_restrict_summary, + num, appLabel, num); + } + + @Override + public int getIconId() { + return mState == StateType.HANDLED + ? R.drawable.ic_perm_device_information_green_24dp + : R.drawable.ic_battery_alert_24dp; + } + + @Override + public void updateState(BatteryTip tip) { + mState = tip.mState; + } + + public List getRestrictAppList() { + return mRestrictAppList; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeTypedList(mRestrictAppList); + } + + public static final Creator CREATOR = new Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new RestrictAppTip(in); + } + + public BatteryTip[] newArray(int size) { + return new RestrictAppTip[size]; + } + }; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java index ddee31461c3..a5815016275 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java @@ -27,7 +27,9 @@ import android.text.format.DateUtils; import com.android.settings.R; import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowRuntimePermissionPresenter; @@ -55,6 +57,7 @@ public class BatteryTipDialogFragmentTest { private BatteryTipDialogFragment mDialogFragment; private Context mContext; private HighUsageTip mHighUsageTip; + private RestrictAppTip mRestrictedAppTip; @Before public void setUp() { @@ -67,6 +70,7 @@ public class BatteryTipDialogFragmentTest { highUsageTips.add(new AppInfo.Builder().setScreenOnTimeMs(SCREEN_TIME_MS).setPackageName( PACKAGE_NAME).build()); mHighUsageTip = new HighUsageTip(SCREEN_TIME_MS, highUsageTips); + mRestrictedAppTip = new RestrictAppTip(BatteryTip.StateType.NEW, highUsageTips); } @Test @@ -82,5 +86,19 @@ public class BatteryTipDialogFragmentTest { mContext.getString(R.string.battery_tip_dialog_message, "1h")); } + @Test + public void testOnCreateDialog_restrictAppTip_fireRestrictAppDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mRestrictedAppTip); + + FragmentTestUtil.startFragment(mDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getTitle()).isEqualTo("Restrict 1 app"); + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.battery_tip_restrict_app_dialog_message)); + } + } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java index 83b32258009..09e67edbdbc 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java @@ -51,6 +51,7 @@ import java.util.List; public class BatteryTipLoaderTest { private static final int[] TIP_ORDER = { BatteryTip.TipType.SMART_BATTERY_MANAGER, + BatteryTip.TipType.APP_RESTRICTION, BatteryTip.TipType.HIGH_DEVICE_USAGE, BatteryTip.TipType.BATTERY_SAVER, BatteryTip.TipType.LOW_BATTERY, diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java new file mode 100644 index 00000000000..e1dea17c1f6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018 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.batterytip.tips; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Parcel; + +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +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 java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class RestrictAppTipTest { + private static final String PACKAGE_NAME = "com.android.app"; + private static final String DISPLAY_NAME = "app"; + + private Context mContext; + private RestrictAppTip mNewBatteryTip; + private RestrictAppTip mHandledBatteryTip; + private List mUsageAppList; + @Mock + private ApplicationInfo mApplicationInfo; + @Mock + private PackageManager mPackageManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME, + PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_ANY_USER); + doReturn(DISPLAY_NAME).when(mApplicationInfo).loadLabel(mPackageManager); + + mUsageAppList = new ArrayList<>(); + mUsageAppList.add(new AppInfo.Builder() + .setPackageName(PACKAGE_NAME) + .build()); + mNewBatteryTip = new RestrictAppTip(BatteryTip.StateType.NEW, mUsageAppList); + mHandledBatteryTip = new RestrictAppTip(BatteryTip.StateType.HANDLED, mUsageAppList); + } + + @Test + public void testParcelable() { + Parcel parcel = Parcel.obtain(); + mNewBatteryTip.writeToParcel(parcel, mNewBatteryTip.describeContents()); + parcel.setDataPosition(0); + + final RestrictAppTip parcelTip = new RestrictAppTip(parcel); + + assertThat(parcelTip.getType()).isEqualTo(BatteryTip.TipType.APP_RESTRICTION); + assertThat(parcelTip.getState()).isEqualTo(BatteryTip.StateType.NEW); + final AppInfo app = parcelTip.getRestrictAppList().get(0); + assertThat(app.packageName).isEqualTo(PACKAGE_NAME); + } + + @Test + public void testGetTitle_stateNew_showRestrictTitle() { + assertThat(mNewBatteryTip.getTitle(mContext)).isEqualTo("Restrict 1 app"); + } + + @Test + public void testGetTitle_stateHandled_showHandledTitle() { + assertThat(mHandledBatteryTip.getTitle(mContext)).isEqualTo("1 recently restricted"); + } + + @Test + public void testGetSummary_stateNew_showRestrictSummary() { + assertThat(mNewBatteryTip.getSummary(mContext)).isEqualTo( + "app has high battery usage"); + } + + @Test + public void testGetSummary_stateHandled_showHandledSummary() { + assertThat(mHandledBatteryTip.getSummary(mContext)).isEqualTo( + "App changes are in progress"); + } +} From 99a2de41ef8ff7eb4ea47413d5ad8c930c00d881 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Thu, 25 Jan 2018 18:00:01 -0800 Subject: [PATCH 9/9] Add restrict and unrestrict dialog Add a fake unrestrict tip so we could reuse the BatteryTipDialogFragment to build the unrestrict dialog. After this cl, restrict dialog has two types: 1. dialog to only restrict one app 2. dialog to restrict more than one app Will add dialog to restrict more than 5 apps when strings are finalized. Bug: 72385333 Bug: 72227981 Test: RunSettingsRoboTests Change-Id: Ib0328f0386efad525b331fd713dd15d060a1a649 --- res/values/strings.xml | 13 +++ .../fuelgauge/AdvancedPowerUsageDetail.java | 8 +- ...ackgroundActivityPreferenceController.java | 79 ++++------------- .../batterytip/BatteryTipDialogFragment.java | 37 +++++--- .../fuelgauge/batterytip/BatteryTipUtils.java | 11 ++- .../batterytip/actions/RestrictAppAction.java | 57 +++++++++++++ .../actions/UnrestrictAppAction.java | 48 +++++++++++ .../fuelgauge/batterytip/tips/BatteryTip.java | 4 +- .../batterytip/tips/RestrictAppTip.java | 11 ++- .../batterytip/tips/UnrestrictAppTip.java | 84 +++++++++++++++++++ ...roundActivityPreferenceControllerTest.java | 20 +---- .../settings/fuelgauge/BatteryUtilsTest.java | 24 ++++++ .../BatteryTipDialogFragmentTest.java | 64 ++++++++++++-- .../actions/RestrictAppActionTest.java | 83 ++++++++++++++++++ .../batterytip/tips/UnrestrictAppTipTest.java | 61 ++++++++++++++ .../testutils/shadow/ShadowUtils.java | 19 +++++ 16 files changed, 517 insertions(+), 106 deletions(-) create mode 100644 src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppAction.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTip.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppActionTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTipTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 1c58a90ea95..3d917c3c6f6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4860,10 +4860,23 @@ App changes are in progress + + + Restrict app? + Restrict %1$d apps? + To save battery, you can stop this app from running in the background when it’s not being used. Restrict + + Remove restriction for %1$s? + + This app will be able to use battery in the background. This may cause your battery to be used up faster. + + Remove + + Not now Smart battery manager diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index de027a3a199..e073456db82 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -46,6 +46,8 @@ import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.fuelgauge.anomaly.AnomalyUtils; +import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment; @@ -69,7 +71,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements ButtonActionDialogFragment.AppButtonsDialogListener, AnomalyDialogFragment.AnomalyDialogListener, LoaderManager.LoaderCallbacks>, - BackgroundActivityPreferenceController.WarningConfirmationListener { + BatteryTipPreferenceController.BatteryTipListener { public static final String TAG = "AdvancedPowerUsageDetail"; public static final String EXTRA_UID = "extra_uid"; @@ -373,8 +375,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements } @Override - public void onLimitBackgroundActivity() { - mBackgroundActivityPreferenceController.setRestricted( + public void onBatteryTipHandled(BatteryTip batteryTip) { + mBackgroundActivityPreferenceController.updateSummary( findPreference(mBackgroundActivityPreferenceController.getPreferenceKey())); } } diff --git a/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java b/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java index 01e41825074..21bd4b73181 100644 --- a/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java @@ -14,27 +14,22 @@ package com.android.settings.fuelgauge; -import android.app.AlertDialog; import android.app.AppOpsManager; -import android.app.Dialog; import android.app.Fragment; import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; import android.os.UserManager; 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.R; import com.android.settings.Utils; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.fuelgauge.batterytip.BatteryTipDialogFragment; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.fuelgauge.PowerWhitelistBackend; @@ -99,15 +94,6 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo return mTargetPackage != null; } - /** - * Called from the warning dialog, if the user decides to go ahead and disable background - * activity for this package - */ - public void setRestricted(Preference preference) { - mBatteryUtils.setForceAppStandby(mUid, mTargetPackage, AppOpsManager.MODE_IGNORED); - updateSummary(preference); - } - @Override public String getPreferenceKey() { return KEY_BACKGROUND_ACTIVITY; @@ -119,20 +105,13 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo final int mode = mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage); final boolean restricted = mode == AppOpsManager.MODE_IGNORED; - if (!restricted) { - showDialog(); - return false; - } - mBatteryUtils.setForceAppStandby(mUid, mTargetPackage, AppOpsManager.MODE_ALLOWED); - updateSummary(preference); - return true; + showDialog(restricted); } return false; } - @VisibleForTesting - void updateSummary(Preference preference) { + public void updateSummary(Preference preference) { if (mPowerWhitelistBackend.isWhitelisted(mTargetPackage)) { preference.setSummary(R.string.background_activity_summary_whitelisted); return; @@ -150,42 +129,16 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo } @VisibleForTesting - void showDialog() { - final WarningDialogFragment dialogFragment = new WarningDialogFragment(); + void showDialog(boolean restricted) { + final AppInfo appInfo = new AppInfo.Builder() + .setPackageName(mTargetPackage) + .build(); + BatteryTip tip = restricted + ? new UnrestrictAppTip(BatteryTip.StateType.NEW, appInfo) + : new RestrictAppTip(BatteryTip.StateType.NEW, appInfo); + + final BatteryTipDialogFragment dialogFragment = BatteryTipDialogFragment.newInstance(tip); dialogFragment.setTargetFragment(mFragment, 0 /* requestCode */); dialogFragment.show(mFragment.getFragmentManager(), TAG); } - - interface WarningConfirmationListener { - void onLimitBackgroundActivity(); - } - - /** - * Warning dialog to show to the user as turning off background activity can lead to - * apps misbehaving as their background task scheduling guarantees will no longer be honored. - */ - public static class WarningDialogFragment extends InstrumentedDialogFragment { - @Override - public int getMetricsCategory() { - // TODO (b/65494831): add metric id - return 0; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final WarningConfirmationListener listener = - (WarningConfirmationListener) getTargetFragment(); - return new AlertDialog.Builder(getContext()) - .setTitle(R.string.background_activity_warning_dialog_title) - .setMessage(R.string.background_activity_warning_dialog_text) - .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - listener.onLimitBackgroundActivity(); - } - }) - .setNegativeButton(R.string.dlg_cancel, null) - .create(); - } - } } diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java index 6d9aaabb810..66ce3caad01 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java @@ -35,6 +35,7 @@ import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; import java.util.List; @@ -89,20 +90,36 @@ public class BatteryTipDialogFragment extends InstrumentedDialogFragment impleme .create(); case BatteryTip.TipType.APP_RESTRICTION: final RestrictAppTip restrictAppTip = (RestrictAppTip) mBatteryTip; - final RecyclerView restrictionView = (RecyclerView) LayoutInflater.from( - context).inflate(R.layout.recycler_view, null); final List restrictedAppList = restrictAppTip.getRestrictAppList(); final int num = restrictedAppList.size(); - restrictionView.setLayoutManager(new LinearLayoutManager(context)); - restrictionView.setAdapter(new HighUsageAdapter(context, restrictedAppList)); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setTitle(context.getResources().getQuantityString( + R.plurals.battery_tip_restrict_app_dialog_title, num, num)) + .setMessage(getString(R.string.battery_tip_restrict_app_dialog_message)) + .setPositiveButton(R.string.battery_tip_restrict_app_dialog_ok, this) + .setNegativeButton(android.R.string.cancel, null); + + // TODO(b/72385333): consider building dialog with 5+ apps when strings are done + if (num > 1) { + final RecyclerView restrictionView = (RecyclerView) LayoutInflater.from( + context).inflate(R.layout.recycler_view, null); + restrictionView.setLayoutManager(new LinearLayoutManager(context)); + restrictionView.setAdapter(new HighUsageAdapter(context, restrictedAppList)); + builder.setView(restrictionView); + } + + return builder.create(); + case BatteryTip.TipType.REMOVE_APP_RESTRICTION: + final UnrestrictAppTip unrestrictAppTip = (UnrestrictAppTip) mBatteryTip; + final CharSequence name = Utils.getApplicationLabel(context, + unrestrictAppTip.getPackageName()); return new AlertDialog.Builder(context) - .setTitle(context.getResources().getQuantityString( - R.plurals.battery_tip_restrict_title, num, num)) - .setMessage(getString(R.string.battery_tip_restrict_app_dialog_message)) - .setView(restrictionView) - .setPositiveButton(R.string.battery_tip_restrict_app_dialog_ok, this) - .setNegativeButton(android.R.string.cancel, null) + .setTitle(getString(R.string.battery_tip_unrestrict_app_dialog_title, name)) + .setMessage(R.string.battery_tip_unrestrict_app_dialog_message) + .setPositiveButton(R.string.battery_tip_unrestrict_app_dialog_ok, this) + .setNegativeButton(R.string.battery_tip_unrestrict_app_dialog_cancel, null) .create(); default: throw new IllegalArgumentException("unknown type " + mBatteryTip.getType()); diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java index 5781afd61f2..5eec3229f80 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java @@ -17,12 +17,17 @@ package com.android.settings.fuelgauge.batterytip; import android.app.Fragment; +import android.content.Context; import com.android.settings.SettingsActivity; import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction; import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction; +import com.android.settings.fuelgauge.batterytip.actions.RestrictAppAction; import com.android.settings.fuelgauge.batterytip.actions.SmartBatteryAction; +import com.android.settings.fuelgauge.batterytip.actions.UnrestrictAppAction; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; /** * Utility class for {@link BatteryTip} @@ -42,7 +47,11 @@ public class BatteryTipUtils { case BatteryTip.TipType.SMART_BATTERY_MANAGER: return new SmartBatteryAction(settingsActivity, fragment); case BatteryTip.TipType.BATTERY_SAVER: - return new BatterySaverAction(settingsActivity.getApplicationContext()); + return new BatterySaverAction(settingsActivity); + case BatteryTip.TipType.APP_RESTRICTION: + return new RestrictAppAction(settingsActivity, (RestrictAppTip) batteryTip); + case BatteryTip.TipType.REMOVE_APP_RESTRICTION: + return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip); default: return null; } diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppAction.java new file mode 100644 index 00000000000..9c49822bb18 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppAction.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 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.batterytip.actions; + +import android.app.AppOpsManager; +import android.content.Context; +import android.support.annotation.VisibleForTesting; + +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; + +import java.util.List; + +/** + * Action to restrict the apps, then app is not allowed to run in the background. + */ +public class RestrictAppAction extends BatteryTipAction { + private RestrictAppTip mRestrictAppTip; + @VisibleForTesting + BatteryUtils mBatteryUtils; + + public RestrictAppAction(Context context, RestrictAppTip tip) { + super(context); + mRestrictAppTip = tip; + mBatteryUtils = BatteryUtils.getInstance(context); + } + + /** + * Handle the action when user clicks positive button + */ + @Override + public void handlePositiveAction() { + final List appInfos = mRestrictAppTip.getRestrictAppList(); + + for (int i = 0, size = appInfos.size(); i < size; i++) { + final String packageName = appInfos.get(i).packageName; + // Force app standby, then app can't run in the background + mBatteryUtils.setForceAppStandby(mBatteryUtils.getPackageUid(packageName), packageName, + AppOpsManager.MODE_IGNORED); + } + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java new file mode 100644 index 00000000000..16812f69afa --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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.batterytip.actions; + +import android.app.AppOpsManager; +import android.content.Context; + +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; + +/** + * Action to clear the restriction to the app + */ +public class UnrestrictAppAction extends BatteryTipAction { + private UnrestrictAppTip mUnRestrictAppTip; + private BatteryUtils mBatteryUtils; + + public UnrestrictAppAction(Context context, UnrestrictAppTip tip) { + super(context); + mUnRestrictAppTip = tip; + mBatteryUtils = BatteryUtils.getInstance(context); + } + + /** + * Handle the action when user clicks positive button + */ + @Override + public void handlePositiveAction() { + final String packageName = mUnRestrictAppTip.getPackageName(); + // Clear force app standby, then app can run in the background + mBatteryUtils.setForceAppStandby(mBatteryUtils.getPackageUid(packageName), packageName, + AppOpsManager.MODE_ALLOWED); + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java index 09ebc4b5b14..59cd5ee4226 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java @@ -50,7 +50,8 @@ public abstract class BatteryTip implements Comparable, Parcelable { TipType.SMART_BATTERY_MANAGER, TipType.APP_RESTRICTION, TipType.REDUCED_BATTERY, - TipType.LOW_BATTERY}) + TipType.LOW_BATTERY, + TipType.REMOVE_APP_RESTRICTION}) public @interface TipType { int SMART_BATTERY_MANAGER = 0; int APP_RESTRICTION = 1; @@ -59,6 +60,7 @@ public abstract class BatteryTip implements Comparable, Parcelable { int REDUCED_BATTERY = 4; int LOW_BATTERY = 5; int SUMMARY = 6; + int REMOVE_APP_RESTRICTION = 7; } private static final String KEY_PREFIX = "key_battery_tip"; diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java index 054b6e19421..1d84d7fb94b 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java @@ -25,6 +25,7 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.fuelgauge.batterytip.AppInfo; +import java.util.ArrayList; import java.util.List; /** @@ -33,9 +34,15 @@ import java.util.List; public class RestrictAppTip extends BatteryTip { private List mRestrictAppList; - public RestrictAppTip(@StateType int state, List highUsageApps) { + public RestrictAppTip(@StateType int state, List restrictApps) { super(TipType.APP_RESTRICTION, state, true /* showDialog */); - mRestrictAppList = highUsageApps; + mRestrictAppList = restrictApps; + } + + public RestrictAppTip(@StateType int state, AppInfo appInfo) { + super(TipType.APP_RESTRICTION, state, true /* showDialog */); + mRestrictAppList = new ArrayList<>(); + mRestrictAppList.add(appInfo); } @VisibleForTesting diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTip.java new file mode 100644 index 00000000000..ec67f6a47b1 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTip.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.batterytip.tips; + +import android.content.Context; +import android.os.Parcel; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.fuelgauge.batterytip.AppInfo; + +/** + * Tip to suggest user to remove app restriction. This is the empty tip and it is only used in + * {@link com.android.settings.fuelgauge.AdvancedPowerUsageDetail} to create dialog. + */ +public class UnrestrictAppTip extends BatteryTip { + private AppInfo mAppInfo; + + public UnrestrictAppTip(@StateType int state, AppInfo appInfo) { + super(TipType.REMOVE_APP_RESTRICTION, state, true /* showDialog */); + mAppInfo = appInfo; + } + + @VisibleForTesting + UnrestrictAppTip(Parcel in) { + super(in); + mAppInfo = in.readParcelable(getClass().getClassLoader()); + } + + @Override + public CharSequence getTitle(Context context) { + // Don't need title since this is an empty tip + return null; + } + + @Override + public CharSequence getSummary(Context context) { + // Don't need summary since this is an empty tip + return null; + } + + @Override + public int getIconId() { + return 0; + } + + public String getPackageName() { + return mAppInfo.packageName; + } + + @Override + public void updateState(BatteryTip tip) { + mState = tip.mState; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(mAppInfo, flags); + } + + public static final Creator CREATOR = new Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new UnrestrictAppTip(in); + } + + public BatteryTip[] newArray(int size) { + return new UnrestrictAppTip[size]; + } + }; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java index 30fdccb23ec..0cfe135d9c7 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java @@ -142,18 +142,17 @@ public class BackgroundActivityPreferenceControllerTest { mController.handlePreferenceTreeClick(mPreference); - verify(mController).showDialog(); + verify(mController).showDialog(false /* restrict */); } @Test - public void testHandlePreferenceTreeClick_unRestrictApp_setModeAllowed() { + public void testHandlePreferenceTreeClick_unRestrictApp_showDialog() { doReturn(AppOpsManager.MODE_IGNORED).when(mAppOpsManager).checkOpNoThrow(anyInt(), anyInt(), anyString()); mController.handlePreferenceTreeClick(mPreference); - verify(mBatteryUtils).setForceAppStandby(UID_LOW_SDK, LOW_SDK_PACKAGE, - AppOpsManager.MODE_ALLOWED); + verify(mController).showDialog(true /* restrict */); } @Test @@ -211,17 +210,4 @@ public class BackgroundActivityPreferenceControllerTest { public void testIsAvailable_ReturnTrue() { assertThat(mController.isAvailable()).isTrue(); } - - @Test - public void testWarningDialog() { - BackgroundActivityPreferenceController.WarningDialogFragment dialogFragment = - new BackgroundActivityPreferenceController.WarningDialogFragment(); - dialogFragment.setTargetFragment(mFragment, 0); - FragmentTestUtil.startFragment(dialogFragment); - final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); - ShadowAlertDialog shadowDialog = shadowOf(dialog); - final Button okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - shadowDialog.clickOn(okButton.getId()); - verify(mFragment).onLimitBackgroundActivity(); - } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java index fe907510117..6bc6ee71664 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -523,4 +524,27 @@ public class BatteryUtilsTest { public void testIsLegacyApp_SdkLargerOrEqualThanO_ReturnFalse() { assertThat(mBatteryUtils.isLegacyApp(HIGH_SDK_PACKAGE)).isFalse(); } + + @Test + public void testSetForceAppStandby_forcePreOApp_forceTwoRestrictions() { + mBatteryUtils.setForceAppStandby(UID, LOW_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + + // Restrict both OP_RUN_IN_BACKGROUND and OP_RUN_ANY_IN_BACKGROUND + verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID, LOW_SDK_PACKAGE, + AppOpsManager.MODE_IGNORED); + verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID, LOW_SDK_PACKAGE, + AppOpsManager.MODE_IGNORED); + } + + @Test + public void testSetForceAppStandby_forceOApp_forceOneRestriction() { + mBatteryUtils.setForceAppStandby(UID, HIGH_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + + // Don't restrict OP_RUN_IN_BACKGROUND because it is already been restricted for O app + verify(mAppOpsManager, never()).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID, + HIGH_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + // Restrict OP_RUN_ANY_IN_BACKGROUND + verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID, + HIGH_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java index a5815016275..ec7238449dd 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java @@ -30,9 +30,10 @@ import com.android.settings.TestConfig; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.testutils.shadow.ShadowRuntimePermissionPresenter; +import com.android.settings.testutils.shadow.ShadowUtils; import org.junit.Before; import org.junit.Test; @@ -49,15 +50,18 @@ import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, - shadows = ShadowRuntimePermissionPresenter.class) + shadows = ShadowUtils.class) public class BatteryTipDialogFragmentTest { private static final String PACKAGE_NAME = "com.android.app"; + private static final String DISPLAY_NAME = "app"; private static final long SCREEN_TIME_MS = DateUtils.HOUR_IN_MILLIS; private BatteryTipDialogFragment mDialogFragment; private Context mContext; private HighUsageTip mHighUsageTip; - private RestrictAppTip mRestrictedAppTip; + private RestrictAppTip mRestrictedOneAppTip; + private RestrictAppTip mRestrictAppsTip; + private UnrestrictAppTip mUnrestrictAppTip; @Before public void setUp() { @@ -67,10 +71,22 @@ public class BatteryTipDialogFragmentTest { FakeFeatureFactory.setupForTest(); List highUsageTips = new ArrayList<>(); - highUsageTips.add(new AppInfo.Builder().setScreenOnTimeMs(SCREEN_TIME_MS).setPackageName( - PACKAGE_NAME).build()); + final AppInfo appInfo = new AppInfo.Builder() + .setScreenOnTimeMs(SCREEN_TIME_MS) + .setPackageName(PACKAGE_NAME) + .build(); + highUsageTips.add(appInfo); mHighUsageTip = new HighUsageTip(SCREEN_TIME_MS, highUsageTips); - mRestrictedAppTip = new RestrictAppTip(BatteryTip.StateType.NEW, highUsageTips); + + final List restrictApps = new ArrayList<>(); + restrictApps.add(appInfo); + mRestrictedOneAppTip = new RestrictAppTip(BatteryTip.StateType.NEW, + new ArrayList<>(restrictApps)); + restrictApps.add(appInfo); + mRestrictAppsTip = new RestrictAppTip(BatteryTip.StateType.NEW, + new ArrayList<>(restrictApps)); + + mUnrestrictAppTip = new UnrestrictAppTip(BatteryTip.StateType.NEW, appInfo); } @Test @@ -87,18 +103,48 @@ public class BatteryTipDialogFragmentTest { } @Test - public void testOnCreateDialog_restrictAppTip_fireRestrictAppDialog() { - mDialogFragment = BatteryTipDialogFragment.newInstance(mRestrictedAppTip); + public void testOnCreateDialog_restrictOneAppTip_fireRestrictOneAppDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mRestrictedOneAppTip); FragmentTestUtil.startFragment(mDialogFragment); final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); ShadowAlertDialog shadowDialog = shadowOf(dialog); - assertThat(shadowDialog.getTitle()).isEqualTo("Restrict 1 app"); + assertThat(shadowDialog.getTitle()).isEqualTo("Restrict app?"); assertThat(shadowDialog.getMessage()).isEqualTo( mContext.getString(R.string.battery_tip_restrict_app_dialog_message)); } + @Test + public void testOnCreateDialog_restrictAppsTip_fireRestrictAppsDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mRestrictAppsTip); + + FragmentTestUtil.startFragment(mDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getTitle()).isEqualTo("Restrict 2 apps?"); + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.battery_tip_restrict_app_dialog_message)); + assertThat(shadowDialog.getView()).isNotNull(); + } + + @Test + public void testOnCreateDialog_unRestrictAppTip_fireUnRestrictDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mUnrestrictAppTip); + ShadowUtils.setApplicationLabel(PACKAGE_NAME, DISPLAY_NAME); + + FragmentTestUtil.startFragment(mDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getTitle()).isEqualTo("Remove restriction for app?"); + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.battery_tip_unrestrict_app_dialog_message)); + } + } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppActionTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppActionTest.java new file mode 100644 index 00000000000..47785d555b3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppActionTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 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.batterytip.actions; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +import android.app.AppOpsManager; +import android.content.Context; + +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +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 java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class RestrictAppActionTest { + private static final String PACKAGE_NAME_1 = "com.android.app1"; + private static final String PACKAGE_NAME_2 = "com.android.app2"; + + @Mock + private BatteryUtils mBatteryUtils; + private Context mContext; + private RestrictAppAction mRestrictAppAction; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + final List mAppInfos = new ArrayList<>(); + mAppInfos.add(new AppInfo.Builder() + .setPackageName(PACKAGE_NAME_1) + .build()); + mAppInfos.add(new AppInfo.Builder() + .setPackageName(PACKAGE_NAME_2) + .build()); + + mRestrictAppAction = new RestrictAppAction(mContext, new RestrictAppTip( + BatteryTip.StateType.NEW, mAppInfos)); + mRestrictAppAction.mBatteryUtils = mBatteryUtils; + } + + @Test + public void testHandlePositiveAction() { + mRestrictAppAction.handlePositiveAction(); + + verify(mBatteryUtils).setForceAppStandby(anyInt(), eq(PACKAGE_NAME_1), + eq(AppOpsManager.MODE_IGNORED)); + verify(mBatteryUtils).setForceAppStandby(anyInt(), eq(PACKAGE_NAME_2), + eq(AppOpsManager.MODE_IGNORED)); + } + + +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTipTest.java new file mode 100644 index 00000000000..a83a1587b64 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTipTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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.batterytip.tips; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; + +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class UnrestrictAppTipTest { + private static final String PACKAGE_NAME = "com.android.app"; + + private UnrestrictAppTip mBatteryTip; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + AppInfo appInfo = new AppInfo.Builder() + .setPackageName(PACKAGE_NAME) + .build(); + mBatteryTip = new UnrestrictAppTip(BatteryTip.StateType.NEW, appInfo); + } + + @Test + public void testParcelable() { + Parcel parcel = Parcel.obtain(); + mBatteryTip.writeToParcel(parcel, mBatteryTip.describeContents()); + parcel.setDataPosition(0); + + final UnrestrictAppTip parcelTip = new UnrestrictAppTip(parcel); + + assertThat(parcelTip.getType()).isEqualTo(BatteryTip.TipType.REMOVE_APP_RESTRICTION); + assertThat(parcelTip.getState()).isEqualTo(BatteryTip.StateType.NEW); + assertThat(parcelTip.getPackageName()).isEqualTo(PACKAGE_NAME); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java index 06910862d3b..b4169913a8d 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java @@ -27,6 +27,9 @@ import com.android.settings.wrapper.FingerprintManagerWrapper; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import java.util.HashMap; +import java.util.Map; + @Implements(Utils.class) public class ShadowUtils { @@ -34,6 +37,7 @@ public class ShadowUtils { private static boolean sIsUserAMonkey; private static boolean sIsDemoUser; private static ComponentName sDeviceOwnerComponentName; + private static Map sAppNameMap; @Implementation public static int enforceSameOwner(Context context, int userId) { @@ -89,4 +93,19 @@ public class ShadowUtils { public static int getManagedProfileId(UserManager um, int parentUserId) { return UserHandle.USER_NULL; } + + @Implementation + public static CharSequence getApplicationLabel(Context context, String packageName) { + if (sAppNameMap != null) { + return sAppNameMap.get(packageName); + } + return null; + } + + public static void setApplicationLabel(String packageName, String appLabel) { + if (sAppNameMap == null) { + sAppNameMap = new HashMap<>(); + } + sAppNameMap.put(packageName, appLabel); + } }