diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 98afa5af12e..fbc7490957a 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/res/values/strings.xml b/res/values/strings.xml index 4b2c67344dc..cae56078d8f 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 @@ -4842,6 +4842,41 @@ 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 + + + + 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/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/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/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/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/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/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 b51474defaa..66ce3caad01 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java @@ -34,6 +34,10 @@ 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 com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; + +import java.util.List; /** * Dialog Fragment to show action dialog for each anomaly @@ -84,6 +88,39 @@ 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 List restrictedAppList = restrictAppTip.getRestrictAppList(); + final int num = restrictedAppList.size(); + + 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(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/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/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/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 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/detectors/RestrictAppDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/RestrictAppDetector.java new file mode 100644 index 00000000000..46e241a24d5 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/RestrictAppDetector.java @@ -0,0 +1,46 @@ +/* + * 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.detectors; + +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; + +import java.util.ArrayList; +import java.util.List; + +/** + * Detector whether to show summary tip. This detector should be executed as the last + * {@link BatteryTipDetector} since it need the most up-to-date {@code visibleTips} + */ +public class RestrictAppDetector implements BatteryTipDetector { + private BatteryTipPolicy mPolicy; + + public RestrictAppDetector(BatteryTipPolicy policy) { + mPolicy = policy; + } + + @Override + public BatteryTip detect() { + // TODO(b/70570352): Detect restrict apps here, get data from database + final List 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/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 new file mode 100644 index 00000000000..1d84d7fb94b --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java @@ -0,0 +1,106 @@ +/* + * 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.ArrayList; +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 restrictApps) { + super(TipType.APP_RESTRICTION, state, true /* showDialog */); + mRestrictAppList = restrictApps; + } + + public RestrictAppTip(@StateType int state, AppInfo appInfo) { + super(TipType.APP_RESTRICTION, state, true /* showDialog */); + mRestrictAppList = new ArrayList<>(); + mRestrictAppList.add(appInfo); + } + + @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/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/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/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/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); } /** 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/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); } } 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 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 ddee31461c3..ec7238449dd 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java @@ -27,10 +27,13 @@ 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.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; @@ -47,14 +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 mRestrictedOneAppTip; + private RestrictAppTip mRestrictAppsTip; + private UnrestrictAppTip mUnrestrictAppTip; @Before public void setUp() { @@ -64,9 +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); + + 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 @@ -82,5 +102,49 @@ public class BatteryTipDialogFragmentTest { mContext.getString(R.string.battery_tip_dialog_message, "1h")); } + @Test + 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 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/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/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/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"); + } +} 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/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; + } +} 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); + } } 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); + } } 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); - } }