diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5a4db20d8aa..fc5b8304f84 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1004,6 +1004,34 @@ android:value="true" /> + + + + + + + + + + + + + + + + + + + + - - + - + + + + + @@ -2227,15 +2258,10 @@ - + - - - - - @@ -2686,10 +2712,6 @@ - - - - @@ -3200,99 +3222,6 @@ android:permission="android.permission.DUMP" android:enabled="@bool/config_has_help" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/res/layout/zen_mode_settings_button.xml b/res/layout/zen_mode_settings_button.xml index 82989fc7863..4fe522d799f 100644 --- a/res/layout/zen_mode_settings_button.xml +++ b/res/layout/zen_mode_settings_button.xml @@ -30,7 +30,7 @@ android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" - android:layout_gravity="center" + android:layout_gravity="left" android:text="@string/zen_mode_button_turn_on" android:paddingEnd="8dp" /> @@ -40,7 +40,7 @@ android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" - android:layout_gravity="center" + android:layout_gravity="left" android:text="@string/zen_mode_button_turn_off" android:paddingEnd="8dp" /> diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 7ab9afbb008..cb6f9be5d04 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -266,14 +266,16 @@ PWD - + + @string/wifi_ap_choose_auto @string/wifi_ap_choose_2G @string/wifi_ap_choose_5G + @string/wifi_ap_choose_auto @string/wifi_ap_choose_2G diff --git a/res/values/config.xml b/res/values/config.xml index 097350b0b81..ce611649452 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -44,6 +44,10 @@ false + + false + com.android.settings.overlay.FeatureFactoryImpl diff --git a/res/values/dimens.xml b/res/values/dimens.xml index d4071ed359a..16ac1281ae0 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -106,13 +106,9 @@ 24dp - - 16dp - 16dp - 72dp - 24dp + 16dp Disabled by admin + + Show lockdown option + + Display power button option that turns off extended access and fingerprint unlocking. None @@ -1116,14 +1120,18 @@ - - Set screen lock + + Set screen lock for security - - Protect your device + + Prevent others from using your tablet + + Prevent others from using your device + + Prevent others from using your phone - - Use fingerprint + + Unlock with fingerprint Unlock with your fingerprint @@ -1941,6 +1949,8 @@ Show password Select AP Band + + Auto 2.4 GHz Band @@ -2247,7 +2257,7 @@ Turn on Wi-Fi Calling - Use Wi-Fi instead of mobile network + Extend coverage by calling over Wi-Fi Calling preference @@ -5588,10 +5598,12 @@ Add account Work profile isn\u2019t available yet - - Work mode - - Allow work profile to function, including apps, background sync, and related features + + Work profile + + Managed by your organization + + Apps and notifications are off Remove work profile @@ -6437,6 +6449,7 @@ + Account for content @@ -6845,6 +6858,9 @@ Do Not Disturb + + Turn on Do Not Disturb + Behavior @@ -6872,11 +6888,11 @@ Automatic rule - - Set Do Not Disturb rules + + Silence phone at certain times - Limit sounds & vibrations at certain times + Set Do Not Disturb rules Use rule @@ -6902,6 +6918,9 @@ Add + + Turn on + Turn on now @@ -6920,6 +6939,12 @@ Do Not Disturb was automatically turned on by an app (%s) + + Priority only + + + %1$s. %2$s + Work profile sounds @@ -6965,6 +6990,9 @@ Notifications + + Recently sent + Advanced @@ -8415,7 +8443,7 @@ Set Night Light schedule - Tint screen amber to help you fall asleep + Automatically tint screen every night Night Light is on @@ -8696,7 +8724,10 @@ Ranking object doesn\'t contain this key. - Emulate a display with a cutout + Simulate a display with a cutout + + + None Special app access @@ -8834,9 +8865,6 @@ Open camera quickly - - Press power button twice to open camera - Flip camera @@ -8846,13 +8874,6 @@ Take selfies faster - - Double-twist phone for selfie mode - - Double-twist tablet for selfie mode - - Double-twist device for selfie mode - Double-tap to check phone @@ -8863,9 +8884,6 @@ To check time, notification icons, and other info, double-tap your screen. - - Check notifications when screen is off - Lift to check phone @@ -8880,9 +8898,6 @@ To check time, notification icons, and other info, pick up your device. - - Check notifications when screen is off - Swipe fingerprint for notifications @@ -8898,9 +8913,6 @@ See notifications quickly - - Swipe down on the fingerprint sensor - @@ -9223,4 +9235,15 @@ [DO NOT TRANSLATE] --> + + My Phone + + My Tablet + + My Device + + Account + + Device name + diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index 21904e671a9..520ebaa5fa8 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -19,8 +19,28 @@ android:key="configure_notification_settings"> + android:key="recent_notifications_category" + android:title="@string/recent_notifications" + android:order="-200"> + + + + + + + + + + + + + - diff --git a/res/xml/managed_profile_settings.xml b/res/xml/managed_profile_settings.xml index 58fcd8802a6..c283e13ca52 100644 --- a/res/xml/managed_profile_settings.xml +++ b/res/xml/managed_profile_settings.xml @@ -20,14 +20,13 @@ + android:title="@string/work_mode_label" + android:summary="@string/summary_placeholder"/> + settings:useAdditionalSummary="true"/> \ No newline at end of file diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml new file mode 100644 index 00000000000..673b2a5722a --- /dev/null +++ b/res/xml/my_device_info.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/security_lockscreen_settings.xml b/res/xml/security_lockscreen_settings.xml index 7a45bec59b0..6faedd72972 100644 --- a/res/xml/security_lockscreen_settings.xml +++ b/res/xml/security_lockscreen_settings.xml @@ -33,6 +33,11 @@ android:title="@string/owner_info_settings_title" android:summary="@string/owner_info_settings_summary" /> + + diff --git a/src/com/android/settings/ApnPreference.java b/src/com/android/settings/ApnPreference.java index 4e89efc781c..9a6eeaf46da 100755 --- a/src/com/android/settings/ApnPreference.java +++ b/src/com/android/settings/ApnPreference.java @@ -16,6 +16,7 @@ package com.android.settings; +import static android.provider.Telephony.Carriers.CONTENT_URI; import static android.provider.Telephony.Carriers.FILTERED_URI; import android.content.ContentUris; @@ -36,6 +37,7 @@ import android.widget.RelativeLayout; public class ApnPreference extends Preference implements CompoundButton.OnCheckedChangeListener, OnClickListener { final static String TAG = "ApnPreference"; + private boolean mDpcEnforced = false; private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -119,7 +121,8 @@ public class ApnPreference extends Preference implements Context context = getContext(); if (context != null) { int pos = Integer.parseInt(getKey()); - Uri url = ContentUris.withAppendedId(FILTERED_URI, pos); + Uri url = ContentUris.withAppendedId( + mDpcEnforced ? FILTERED_URI : CONTENT_URI, pos); Intent editIntent = new Intent(Intent.ACTION_EDIT, url); editIntent.putExtra(ApnSettings.SUB_ID, mSubId); context.startActivity(editIntent); @@ -138,4 +141,8 @@ public class ApnPreference extends Preference implements public void setSubId(int subId) { mSubId = subId; } + + public void setDpcEnforced(boolean enforced) { + mDpcEnforced = enforced; + } } diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java index 3628121808e..2c22a790e8d 100755 --- a/src/com/android/settings/ApnSettings.java +++ b/src/com/android/settings/ApnSettings.java @@ -16,6 +16,7 @@ package com.android.settings; +import static android.provider.Telephony.Carriers.CONTENT_URI; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; import static android.provider.Telephony.Carriers.FILTERED_URI; @@ -291,6 +292,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements mSelectedKey = getSelectedApnKey(); cursor.moveToFirst(); + boolean enforced = isDpcApnEnforced(); while (!cursor.isAfterLast()) { String name = cursor.getString(NAME_INDEX); String apn = cursor.getString(APN_INDEX); @@ -307,6 +309,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements pref.setPersistent(false); pref.setOnPreferenceChangeListener(this); pref.setSubId(subId); + pref.setDpcEnforced(enforced); boolean selectable = ((type == null) || !type.equals("mms")); pref.setSelectable(selectable); @@ -398,7 +401,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements @Override public boolean onPreferenceTreeClick(Preference preference) { int pos = Integer.parseInt(preference.getKey()); - Uri url = ContentUris.withAppendedId(FILTERED_URI, pos); + Uri url = ContentUris.withAppendedId(isDpcApnEnforced() ? FILTERED_URI : CONTENT_URI, pos); startActivity(new Intent(Intent.ACTION_EDIT, url)); return true; } diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index d13a62dcbb3..dcf7ed508c7 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -16,7 +16,6 @@ package com.android.settings; -import static com.android.settings.core.FeatureFlags.BATTERY_SETTINGS_V2; import static com.android.settings.core.FeatureFlags.CONNECTED_DEVICE_V2; import android.os.Bundle; @@ -55,6 +54,7 @@ public class Settings extends SettingsActivity { public static class NightDisplaySettingsActivity extends SettingsActivity { /* empty */ } public static class NightDisplaySuggestionActivity extends NightDisplaySettingsActivity { /* empty */ } public static class DeviceInfoSettingsActivity extends SettingsActivity { /* empty */ } + public static class MeCardActivity extends SettingsActivity { /* empty */ } public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ } public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ } public static class ManageAssistActivity extends SettingsActivity { /* empty */ } @@ -133,16 +133,8 @@ public class Settings extends SettingsActivity { public static class AppWriteSettingsActivity extends SettingsActivity { /* empty */ } public static class AdvancedAppsActivity extends SettingsActivity { /* empty */ } - public static class ManageExternalSourcesActivity extends SettingsActivity { - /* empty */ } + public static class ManageExternalSourcesActivity extends SettingsActivity {/* empty */ } public static class ManageAppExternalSourcesActivity extends SettingsActivity { /* empty */ } - public static class DoubleTapPowerSuggestionActivity extends SettingsActivity { /* empty */ } - public static class DoubleTwistSuggestionActivity extends SettingsActivity { /* empty */ } - public static class AmbientDisplaySuggestionActivity extends SettingsActivity { /* empty */ } - public static class AmbientDisplayPickupSuggestionActivity extends SettingsActivity { - /* empty */ } - public static class SwipeToNotificationSuggestionActivity extends SettingsActivity { - /* empty */ } public static class WallpaperSettingsActivity extends SettingsActivity { /* empty */ } public static class ManagedProfileSettingsActivity extends SettingsActivity { /* empty */ } public static class DeletionHelperActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index d3580d18b86..3ac268a7927 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -877,6 +877,19 @@ public class SettingsActivity extends SettingsDrawerActivity WifiDisplaySettings.isAvailable(this), isAdmin) || somethingChanged; + // Enable/disable the Me Card page. + final boolean isMeCardEnabled = featureFactory + .getAccountFeatureProvider() + .isMeCardEnabled(this); + somethingChanged = setTileEnabled(new ComponentName(packageName, + Settings.MeCardActivity.class.getName()), + isMeCardEnabled, isAdmin) + || somethingChanged; + somethingChanged = setTileEnabled(new ComponentName(packageName, + Settings.DeviceInfoSettingsActivity.class.getName()), + !isMeCardEnabled, isAdmin) + || somethingChanged; + if (UserHandle.MU_ENABLED && !isAdmin) { // When on restricted users, disable all extra categories (but only the settings ones). diff --git a/src/com/android/settings/accounts/AccountFeatureProvider.java b/src/com/android/settings/accounts/AccountFeatureProvider.java new file mode 100644 index 00000000000..bbfc48ac17c --- /dev/null +++ b/src/com/android/settings/accounts/AccountFeatureProvider.java @@ -0,0 +1,34 @@ +/* + * 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.accounts; + +import android.accounts.Account; +import android.content.Context; +import android.util.FeatureFlagUtils; + +import com.android.settings.core.FeatureFlags; + +public interface AccountFeatureProvider { + String getAccountType(); + Account[] getAccounts(Context context); + /** + * Checks whether or not to display the new About Phone page. + */ + default boolean isMeCardEnabled(Context context) { + return FeatureFlagUtils.isEnabled(context, FeatureFlags.ABOUT_PHONE_V2); + } +} diff --git a/src/com/android/settings/accounts/AccountFeatureProviderImpl.java b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java new file mode 100644 index 00000000000..90b581ba80e --- /dev/null +++ b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java @@ -0,0 +1,16 @@ +package com.android.settings.accounts; + +import android.accounts.Account; +import android.content.Context; + +public class AccountFeatureProviderImpl implements AccountFeatureProvider { + @Override + public String getAccountType() { + return null; + } + + @Override + public Account[] getAccounts(Context context) { + return new Account[0]; + } +} diff --git a/src/com/android/settings/accounts/ManagedProfileSettings.java b/src/com/android/settings/accounts/ManagedProfileSettings.java index 3ea7cf730d5..09330428d39 100644 --- a/src/com/android/settings/accounts/ManagedProfileSettings.java +++ b/src/com/android/settings/accounts/ManagedProfileSettings.java @@ -104,8 +104,7 @@ public class ManagedProfileSettings extends SettingsPreferenceFragment private void loadDataAndPopulateUi() { if (mWorkModePreference != null) { - mWorkModePreference.setChecked( - !mUserManager.isQuietModeEnabled(mManagedUser)); + updateWorkModePreference(); } if (mContactPrefrence != null) { @@ -124,6 +123,14 @@ public class ManagedProfileSettings extends SettingsPreferenceFragment return MetricsProto.MetricsEvent.ACCOUNTS_WORK_PROFILE_SETTINGS; } + private void updateWorkModePreference() { + boolean isWorkModeOn = !mUserManager.isQuietModeEnabled(mManagedUser); + mWorkModePreference.setChecked(isWorkModeOn); + mWorkModePreference.setSummary(isWorkModeOn + ? R.string.work_mode_on_summary + : R.string.work_mode_off_summary); + } + @Override public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -159,8 +166,7 @@ public class ManagedProfileSettings extends SettingsPreferenceFragment || action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) == mManagedUser.getIdentifier()) { - mWorkModePreference.setChecked( - !mUserManager.isQuietModeEnabled(mManagedUser)); + updateWorkModePreference(); } return; } diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index 4b5ce782a24..4b8ccd10e4e 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -27,4 +27,5 @@ public class FeatureFlags { public static final String SECURITY_SETTINGS_V2 = "settings_security_settings_v2"; public static final String ZONE_PICKER_V2 = "settings_zone_picker_v2"; public static final String SUGGESTION_UI_V2 = "settings_suggestion_ui_v2"; + public static final String ABOUT_PHONE_V2 = "settings_about_phone_v2"; } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 026cc2bd777..2cb1cbfa42b 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -298,5 +298,6 @@ public class SettingsGateway { Settings.DateTimeSettingsActivity.class.getName(), Settings.DeviceInfoSettingsActivity.class.getName(), Settings.EnterprisePrivacySettingsActivity.class.getName(), + Settings.MeCardActivity.class.getName(), }; } diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java index fe19b958c2d..05c1effe8b6 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java @@ -30,20 +30,10 @@ import android.util.Pair; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.Settings.AmbientDisplayPickupSuggestionActivity; -import com.android.settings.Settings.AmbientDisplaySuggestionActivity; -import com.android.settings.Settings.DoubleTapPowerSuggestionActivity; -import com.android.settings.Settings.DoubleTwistSuggestionActivity; import com.android.settings.Settings.NightDisplaySuggestionActivity; -import com.android.settings.Settings.SwipeToNotificationSuggestionActivity; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.fingerprint.FingerprintEnrollSuggestionActivity; import com.android.settings.fingerprint.FingerprintSuggestionActivity; -import com.android.settings.gestures.DoubleTapPowerPreferenceController; -import com.android.settings.gestures.DoubleTapScreenPreferenceController; -import com.android.settings.gestures.DoubleTwistPreferenceController; -import com.android.settings.gestures.PickupGesturePreferenceController; -import com.android.settings.gestures.SwipeToNotificationPreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ScreenLockSuggestionActivity; import com.android.settings.support.NewDeviceIntroSuggestionActivity; @@ -98,21 +88,6 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider return hasUsedNightDisplay(context); } else if (className.equals(NewDeviceIntroSuggestionActivity.class.getName())) { return NewDeviceIntroSuggestionActivity.isSuggestionComplete(context); - } else if (className.equals(DoubleTapPowerSuggestionActivity.class.getName())) { - return DoubleTapPowerPreferenceController - .isSuggestionComplete(context, getSharedPrefs(context)); - } else if (className.equals(DoubleTwistSuggestionActivity.class.getName())) { - return DoubleTwistPreferenceController - .isSuggestionComplete(context, getSharedPrefs(context)); - } else if (className.equals(AmbientDisplaySuggestionActivity.class.getName())) { - return DoubleTapScreenPreferenceController - .isSuggestionComplete(context, getSharedPrefs(context)); - } else if (className.equals(AmbientDisplayPickupSuggestionActivity.class.getName())) { - return PickupGesturePreferenceController - .isSuggestionComplete(context, getSharedPrefs(context)); - } else if (className.equals(SwipeToNotificationSuggestionActivity.class.getName())) { - return SwipeToNotificationPreferenceController - .isSuggestionComplete(context, getSharedPrefs(context)); } return false; } diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 0420ecfa1cc..ce4b7bf9d75 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -398,6 +398,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new CameraLaserSensorPreferenceController(context)); controllers.add(new WifiDisplayCertificationPreferenceController(context)); controllers.add(new WifiVerboseLoggingPreferenceController(context)); + controllers.add(new WifiConnectedMacRandomizationPreferenceController(context)); controllers.add(new MobileDataAlwaysOnPreferenceController(context)); controllers.add(new TetheringHardwareAccelPreferenceController(context)); controllers.add(new SelectUsbConfigPreferenceController(context, lifecycle)); diff --git a/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java b/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java index 1035a1b9847..d6c74f911eb 100644 --- a/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java +++ b/src/com/android/settings/development/EmulateDisplayCutoutPreferenceController.java @@ -16,41 +16,52 @@ package com.android.settings.development; +import static android.os.UserHandle.USER_SYSTEM; + import android.content.Context; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; +import android.content.pm.PackageManager; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; -import android.support.v7.preference.TwoStatePreference; +import android.text.TextUtils; +import com.android.internal.util.ArrayUtils; +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.development.DeveloperOptionsPreferenceController; +import java.util.List; + public class EmulateDisplayCutoutPreferenceController extends DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { - private static final String EMULATION_OVERLAY = "com.android.internal.display.cutout.emulation"; + public static final String EMULATION_OVERLAY_PREFIX = + "com.android.internal.display.cutout.emulation."; private static final String KEY = "display_cutout_emulation"; private final IOverlayManager mOverlayManager; private final boolean mAvailable; - private TwoStatePreference mPreference; + private ListPreference mPreference; + private PackageManager mPackageManager; @VisibleForTesting - EmulateDisplayCutoutPreferenceController(Context context, IOverlayManager overlayManager) { + EmulateDisplayCutoutPreferenceController(Context context, PackageManager packageManager, + IOverlayManager overlayManager) { super(context); mOverlayManager = overlayManager; - mAvailable = overlayManager != null && getEmulationOverlayInfo() != null; + mPackageManager = packageManager; + mAvailable = overlayManager != null && getOverlayInfos().length > 0; } public EmulateDisplayCutoutPreferenceController(Context context) { - this(context, IOverlayManager.Stub.asInterface( + this(context, context.getPackageManager(), IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE))); } @@ -67,45 +78,95 @@ public class EmulateDisplayCutoutPreferenceController extends @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - setPreference((TwoStatePreference) screen.findPreference(getPreferenceKey())); + setPreference((ListPreference) screen.findPreference(getPreferenceKey())); } @VisibleForTesting - void setPreference(TwoStatePreference preference) { + void setPreference(ListPreference preference) { mPreference = preference; } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - return writeEnabled((boolean) newValue); + return setEmulationOverlay((String) newValue); } - private boolean writeEnabled(boolean newValue) { - OverlayInfo current = getEmulationOverlayInfo(); - if (current == null || current.isEnabled() == newValue) { - return false; + private boolean setEmulationOverlay(String packageName) { + OverlayInfo[] overlays = getOverlayInfos(); + CharSequence currentPackageName = null; + for (OverlayInfo o : overlays) { + if (o.isEnabled()) { + currentPackageName = o.packageName; + } } - try { - return mOverlayManager.setEnabled(EMULATION_OVERLAY, newValue, UserHandle.USER_SYSTEM); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + + if (TextUtils.isEmpty(packageName) && TextUtils.isEmpty(currentPackageName) + || TextUtils.equals(packageName, currentPackageName)) { + // Already set. + return true; } + + for (OverlayInfo o : overlays) { + boolean isEnabled = o.isEnabled(); + boolean shouldBeEnabled = TextUtils.equals(o.packageName, packageName); + if (isEnabled != shouldBeEnabled) { + try { + mOverlayManager.setEnabled(o.packageName, shouldBeEnabled, USER_SYSTEM); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + updateState(mPreference); + return true; } @Override public void updateState(Preference preference) { - OverlayInfo overlayInfo = getEmulationOverlayInfo(); - mPreference.setChecked(overlayInfo != null && overlayInfo.isEnabled()); + OverlayInfo[] overlays = getOverlayInfos(); + + CharSequence[] pkgs = new CharSequence[overlays.length + 1]; + CharSequence[] labels = new CharSequence[pkgs.length]; + + int current = 0; + pkgs[0] = ""; + labels[0] = mContext.getString(R.string.display_cutout_emulation_none); + + for (int i = 0; i < overlays.length; i++) { + OverlayInfo o = overlays[i]; + pkgs[i+1] = o.packageName; + if (o.isEnabled()) { + current = i+1; + } + } + for (int i = 1; i < pkgs.length; i++) { + try { + labels[i] = mPackageManager.getApplicationInfo(pkgs[i].toString(), 0) + .loadLabel(mPackageManager); + } catch (PackageManager.NameNotFoundException e) { + labels[i] = pkgs[i]; + } + } + + mPreference.setEntries(labels); + mPreference.setEntryValues(pkgs); + mPreference.setValueIndex(current); + mPreference.setSummary(labels[current]); } - private OverlayInfo getEmulationOverlayInfo() { - OverlayInfo overlayInfo = null; + private OverlayInfo[] getOverlayInfos() { try { - overlayInfo = mOverlayManager.getOverlayInfo(EMULATION_OVERLAY, UserHandle.USER_SYSTEM); + @SuppressWarnings("unchecked") List overlayInfos = + mOverlayManager.getOverlayInfosForTarget("android", USER_SYSTEM); + for (int i = overlayInfos.size() - 1; i >= 0; i--) { + if (!overlayInfos.get(i).packageName.startsWith(EMULATION_OVERLAY_PREFIX)) { + overlayInfos.remove(i); + } + } + return overlayInfos.toArray(new OverlayInfo[overlayInfos.size()]); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - return overlayInfo; } @Override @@ -115,8 +176,8 @@ public class EmulateDisplayCutoutPreferenceController extends @Override protected void onDeveloperOptionsSwitchDisabled() { - writeEnabled(false); - mPreference.setChecked(false); + setEmulationOverlay(""); + updateState(mPreference); mPreference.setEnabled(false); } } diff --git a/src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceController.java b/src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceController.java new file mode 100644 index 00000000000..eae2355c7a2 --- /dev/null +++ b/src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceController.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.development; + +import android.content.Context; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +public class WifiConnectedMacRandomizationPreferenceController extends + DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener, + PreferenceControllerMixin { + + private static final String WIFI_CONNECTED_MAC_RANDOMIZATION_KEY = + "wifi_connected_mac_randomization"; + + @VisibleForTesting + static final int SETTING_VALUE_ON = 1; + @VisibleForTesting + static final int SETTING_VALUE_OFF = 0; + + private SwitchPreference mPreference; + + public WifiConnectedMacRandomizationPreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + return mContext.getResources().getBoolean( + R.bool.config_wifi_support_connected_mac_randomization); + } + + @Override + public String getPreferenceKey() { + return WIFI_CONNECTED_MAC_RANDOMIZATION_KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = (SwitchPreference) screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean isEnabled = (Boolean) newValue; + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, + isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); + return true; + } + + @Override + public void updateState(Preference preference) { + final int enableMode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, SETTING_VALUE_OFF); + mPreference.setChecked(enableMode != SETTING_VALUE_OFF); + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + mPreference.setEnabled(true); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, SETTING_VALUE_OFF); + mPreference.setChecked(false); + mPreference.setEnabled(false); + } +} diff --git a/src/com/android/settings/deviceinfo/BrandedAccountPreferenceController.java b/src/com/android/settings/deviceinfo/BrandedAccountPreferenceController.java new file mode 100644 index 00000000000..5565e3de840 --- /dev/null +++ b/src/com/android/settings/deviceinfo/BrandedAccountPreferenceController.java @@ -0,0 +1,78 @@ +/* + * 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.deviceinfo; + +import android.accounts.Account; +import android.content.Context; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.accounts.AccountDetailDashboardFragment; +import com.android.settings.accounts.AccountFeatureProvider; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; + +public class BrandedAccountPreferenceController extends BasePreferenceController { + private static final String KEY_PREFERENCE_TITLE = "account"; + private final Account[] mAccounts; + + public BrandedAccountPreferenceController(Context context) { + super(context, KEY_PREFERENCE_TITLE); + final AccountFeatureProvider accountFeatureProvider = FeatureFactory.getFactory( + mContext).getAccountFeatureProvider(); + mAccounts = accountFeatureProvider.getAccounts(mContext); + } + + @Override + public int getAvailabilityStatus() { + if (mAccounts != null && mAccounts.length > 0) { + return AVAILABLE; + } + return DISABLED_FOR_USER; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final AccountFeatureProvider accountFeatureProvider = FeatureFactory.getFactory( + mContext).getAccountFeatureProvider(); + final Preference accountPreference = screen.findPreference(KEY_PREFERENCE_TITLE); + if (accountPreference != null && (mAccounts == null || mAccounts.length == 0)) { + screen.removePreference(accountPreference); + return; + } + + accountPreference.setSummary(mAccounts[0].name); + accountPreference.setOnPreferenceClickListener(preference -> { + final Bundle args = new Bundle(); + args.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT, + mAccounts[0]); + args.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE, + android.os.Process.myUserHandle()); + args.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_TYPE, + accountFeatureProvider.getAccountType()); + Utils.startWithFragment(mContext, AccountDetailDashboardFragment.class.getName(), + args, null, 0, + R.string.account_sync_title, null, MetricsEvent.ACCOUNT); + return true; + }); + } +} diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java new file mode 100644 index 00000000000..a3018072c30 --- /dev/null +++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java @@ -0,0 +1,180 @@ +/* + * 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.deviceinfo.aboutphone; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserManager; +import android.provider.SearchIndexableResource; +import android.telephony.TelephonyManager; +import android.view.View; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.Utils; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.deviceinfo.BluetoothAddressPreferenceController; +import com.android.settings.deviceinfo.BrandedAccountPreferenceController; +import com.android.settings.deviceinfo.BuildNumberPreferenceController; +import com.android.settings.deviceinfo.DeviceModelPreferenceController; +import com.android.settings.deviceinfo.FccEquipmentIdPreferenceController; +import com.android.settings.deviceinfo.FeedbackPreferenceController; +import com.android.settings.deviceinfo.ImsStatusPreferenceController; +import com.android.settings.deviceinfo.IpAddressPreferenceController; +import com.android.settings.deviceinfo.ManualPreferenceController; +import com.android.settings.deviceinfo.PhoneNumberPreferenceController; +import com.android.settings.deviceinfo.RegulatoryInfoPreferenceController; +import com.android.settings.deviceinfo.SafetyInfoPreferenceController; +import com.android.settings.deviceinfo.WifiMacAddressPreferenceController; +import com.android.settings.deviceinfo.firmwareversion.FirmwareVersionPreferenceController; +import com.android.settings.deviceinfo.imei.ImeiInfoPreferenceController; +import com.android.settings.deviceinfo.simstatus.SimStatusPreferenceController; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settings.widget.EntityHeaderController; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MyDeviceInfoFragment extends DashboardFragment { + private static final String LOG_TAG = "MeCardFragment"; + + private static final String KEY_MY_DEVICE_INFO_HEADER = "my_device_info_header"; + private static final String KEY_LEGAL_CONTAINER = "legal_container"; + + @Override + public int getMetricsCategory() { + return MetricsEvent.DEVICEINFO; + } + + @Override + public int getHelpResource() { + return R.string.help_uri_about; + } + + @Override + public void onResume() { + super.onResume(); + initHeader(); + } + + @Override + protected String getLogTag() { + return LOG_TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.my_device_info; + } + + @Override + protected List getPreferenceControllers(Context context) { + return buildPreferenceControllers(context, getActivity(), this /* fragment */, + getLifecycle()); + } + + private static List buildPreferenceControllers(Context context, + Activity activity, Fragment fragment, Lifecycle lifecycle) { + final List controllers = new ArrayList<>(); + controllers.add(new PhoneNumberPreferenceController(context)); + controllers.add(new BrandedAccountPreferenceController(context)); + controllers.add(new SimStatusPreferenceController(context, fragment)); + controllers.add(new DeviceModelPreferenceController(context, fragment)); + controllers.add(new ImeiInfoPreferenceController(context, fragment)); + controllers.add(new FirmwareVersionPreferenceController(context, fragment)); + controllers.add(new ImsStatusPreferenceController(context, lifecycle)); + controllers.add(new IpAddressPreferenceController(context, lifecycle)); + controllers.add(new WifiMacAddressPreferenceController(context, lifecycle)); + controllers.add(new BluetoothAddressPreferenceController(context, lifecycle)); + controllers.add(new RegulatoryInfoPreferenceController(context)); + controllers.add(new SafetyInfoPreferenceController(context)); + controllers.add(new ManualPreferenceController(context)); + controllers.add(new FeedbackPreferenceController(fragment, context)); + controllers.add(new FccEquipmentIdPreferenceController(context)); + controllers.add( + new BuildNumberPreferenceController(context, activity, fragment, lifecycle)); + // TODO: Add preference controller for getting the device name. + return controllers; + } + + private void initHeader() { + // TODO: Migrate into its own controller. + final LayoutPreference headerPreference = + (LayoutPreference) getPreferenceScreen().findPreference(KEY_MY_DEVICE_INFO_HEADER); + final View appSnippet = headerPreference.findViewById(R.id.entity_header); + final Activity context = getActivity(); + final Bundle bundle = getArguments(); + EntityHeaderController controller = EntityHeaderController + .newInstance(context, this, appSnippet) + .setRecyclerView(getListView(), getLifecycle()) + .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, + EntityHeaderController.ActionType.ACTION_NONE); + + // TODO: There may be an avatar setting action we can use here. + final int iconId = bundle.getInt("icon_id", 0); + if (iconId == 0) { + UserManager userManager = (UserManager) getActivity().getSystemService( + Context.USER_SERVICE); + UserInfo info = Utils.getExistingUser(userManager, android.os.Process.myUserHandle()); + controller.setLabel(info.name); + controller.setIcon( + com.android.settingslib.Utils.getUserIcon(getActivity(), userManager, info)); + } + + controller.done(context, true /* rebindActions */); + } + + /** + * For Search. + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.my_device_info; + return Arrays.asList(sir); + } + + @Override + public List getPreferenceControllers( + Context context) { + return buildPreferenceControllers(context, null /*activity */, + null /* fragment */, null /* lifecycle */); + } + + @Override + public List getNonIndexableKeys(Context context) { + List keys = super.getNonIndexableKeys(context); + // The legal container is duplicated, so we ignore it here. + keys.add(KEY_LEGAL_CONTAINER); + return keys; + } + }; +} diff --git a/src/com/android/settings/fuelgauge/RestrictedAppDetails.java b/src/com/android/settings/fuelgauge/RestrictedAppDetails.java index bdaaa3a66a9..e09a8a38c84 100644 --- a/src/com/android/settings/fuelgauge/RestrictedAppDetails.java +++ b/src/com/android/settings/fuelgauge/RestrictedAppDetails.java @@ -20,11 +20,9 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.support.v14.preference.PreferenceFragment; -import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.CheckBoxPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; @@ -36,9 +34,7 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.fuelgauge.anomaly.Anomaly; -import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment; -import com.android.settings.fuelgauge.anomaly.AnomalyPreference; +import com.android.settings.widget.AppCheckBoxPreference; import com.android.settingslib.core.AbstractPreferenceController; import java.util.List; @@ -120,7 +116,7 @@ public class RestrictedAppDetails extends DashboardFragment { final Context context = getPrefContext(); for (int i = 0, size = mPackageOpsList.size(); i < size; i++) { - final CheckBoxPreference checkBoxPreference = new CheckBoxPreference(context); + final CheckBoxPreference checkBoxPreference = new AppCheckBoxPreference(context); final AppOpsManager.PackageOps packageOps = mPackageOpsList.get(i); try { final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo( @@ -128,6 +124,10 @@ public class RestrictedAppDetails extends DashboardFragment { checkBoxPreference.setChecked(true); checkBoxPreference.setTitle(mPackageManager.getApplicationLabel(applicationInfo)); checkBoxPreference.setKey(packageOps.getPackageName()); + checkBoxPreference.setIcon( + Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, + packageOps.getPackageName(), + UserHandle.getUserId(packageOps.getUid()))); checkBoxPreference.setOnPreferenceChangeListener((pref, value) -> { // change the toggle final int mode = (Boolean) value ? AppOpsManager.MODE_IGNORED diff --git a/src/com/android/settings/location/LocationEnabler.java b/src/com/android/settings/location/LocationEnabler.java index 4dcdac0c456..28ee2138abb 100644 --- a/src/com/android/settings/location/LocationEnabler.java +++ b/src/com/android/settings/location/LocationEnabler.java @@ -34,8 +34,10 @@ import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; import static com.android.settingslib.Utils.updateLocationMode; +import static com.android.settingslib.Utils.updateLocationEnabled; import static com.android.settingslib.RestrictedLockUtils.checkIfRestrictionEnforced; + /** * A class that listens to location settings change and modifies location settings * settings. @@ -106,6 +108,25 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { } } + void setLocationEnabled(boolean enabled) { + final int currentMode = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + + if (isRestricted()) { + // Location toggling disabled by user restriction. Read the current location mode to + // update the location master switch. + if (Log.isLoggable(TAG, Log.INFO)) { + Log.i(TAG, "Restricted user, not setting location mode"); + } + if (mListener != null) { + mListener.onLocationModeChanged(currentMode, true); + } + return; + } + updateLocationEnabled(mContext, enabled, UserHandle.myUserId()); + refreshLocationMode(); + } + void setLocationMode(int mode) { final int currentMode = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index d0fca16629d..85c049db1d5 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -47,15 +47,8 @@ import java.util.List; *
    *
  • Platform location controls
  • *
      - *
    • In switch bar: location master switch. Used to toggle - * {@link android.provider.Settings.Secure#LOCATION_MODE} between - * {@link android.provider.Settings.Secure#LOCATION_MODE_OFF} and another location mode. + *
    • In switch bar: location master switch. Used to toggle location on and off. *
    • - *
    • Mode preference: only available if the master switch is on, selects between - * {@link android.provider.Settings.Secure#LOCATION_MODE} of - * {@link android.provider.Settings.Secure#LOCATION_MODE_HIGH_ACCURACY}, - * {@link android.provider.Settings.Secure#LOCATION_MODE_BATTERY_SAVING}, or - * {@link android.provider.Settings.Secure#LOCATION_MODE_SENSORS_ONLY}.
    • *
    *
  • Recent location requests: automatically populated by {@link RecentLocationApps}
  • *
  • Location services: multi-app settings provided from outside the Android framework. Each diff --git a/src/com/android/settings/location/LocationSwitchBarController.java b/src/com/android/settings/location/LocationSwitchBarController.java index 6522dc75be2..ca1932f9cc6 100644 --- a/src/com/android/settings/location/LocationSwitchBarController.java +++ b/src/com/android/settings/location/LocationSwitchBarController.java @@ -96,9 +96,6 @@ public class LocationSwitchBarController implements SwitchBar.OnSwitchChangeList */ @Override public void onSwitchChanged(Switch switchView, boolean isChecked) { - mLocationEnabler.setLocationMode(isChecked - ? android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS - : android.provider.Settings.Secure.LOCATION_MODE_OFF); + mLocationEnabler.setLocationEnabled(isChecked); } - } diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index ef0f40bb571..333e0600118 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -176,15 +176,6 @@ public class AppNotificationSettings extends NotificationSettingsBase { } else { groupCategory.setTitle(group.getName()); groupCategory.setKey(group.getId()); - Bundle groupArgs = new Bundle(); - groupArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid); - groupArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg); - groupArgs.putString(Settings.EXTRA_CHANNEL_GROUP_ID, group.getId()); - Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(), - ChannelGroupNotificationSettings.class.getName(), - groupArgs, null, R.string.notification_group_title, - null, false, getMetricsCategory()); - groupCategory.setIntent(channelIntent); populateGroupToggle(groupCategory, group); } diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java index 253346683cc..7cfa1248245 100644 --- a/src/com/android/settings/notification/ConfigureNotificationSettings.java +++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java @@ -17,6 +17,8 @@ package com.android.settings.notification; import android.app.Activity; +import android.app.Application; +import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -77,11 +79,18 @@ public class ConfigureNotificationSettings extends DashboardFragment { @Override protected List getPreferenceControllers(Context context) { - return buildPreferenceControllers(context, getLifecycle()); + final Activity activity = getActivity(); + final Application app; + if (activity != null) { + app = activity.getApplication(); + } else { + app = null; + } + return buildPreferenceControllers(context, getLifecycle(), app, this); } private static List buildPreferenceControllers(Context context, - Lifecycle lifecycle) { + Lifecycle lifecycle, Application app, Fragment host) { final List controllers = new ArrayList<>(); final BadgingNotificationPreferenceController badgeController = new BadgingNotificationPreferenceController(context); @@ -96,6 +105,8 @@ public class ConfigureNotificationSettings extends DashboardFragment { lifecycle.addObserver(pulseController); lifecycle.addObserver(lockScreenNotificationController); } + controllers.add(new RecentNotifyingAppsPreferenceController( + context, new NotificationBackend(), app, host)); controllers.add(new SwipeToNotificationPreferenceController(context, lifecycle, KEY_SWIPE_DOWN)); controllers.add(badgeController); @@ -167,7 +178,7 @@ public class ConfigureNotificationSettings extends DashboardFragment { @Override public List getPreferenceControllers( Context context) { - return buildPreferenceControllers(context, null); + return buildPreferenceControllers(context, null, null, null); } @Override diff --git a/src/com/android/settings/notification/EnableZenModeDialog.java b/src/com/android/settings/notification/EnableZenModeDialog.java new file mode 100644 index 00000000000..f683a2116cb --- /dev/null +++ b/src/com/android/settings/notification/EnableZenModeDialog.java @@ -0,0 +1,467 @@ +package com.android.settings.notification; + +/* + * 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. + */ + +import static android.util.Log.wtf; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.NotificationManager; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Objects; + +public class EnableZenModeDialog extends InstrumentedDialogFragment { + + private static final String TAG = "EnableZenModeDialog"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS; + private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0]; + private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1]; + private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60); + + @VisibleForTesting + public static final int FOREVER_CONDITION_INDEX = 0; + @VisibleForTesting + public static final int COUNTDOWN_CONDITION_INDEX = 1; + @VisibleForTesting + public static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2; + @VisibleForTesting + protected Activity mActivity; + + private static final int SECONDS_MS = 1000; + private static final int MINUTES_MS = 60 * SECONDS_MS; + + @VisibleForTesting + protected Uri mForeverId; + private int mBucketIndex = -1; + + private AlarmManager mAlarmManager; + private int mUserId; + private boolean mAttached; + + @VisibleForTesting + protected Context mContext; + + private RadioGroup mZenRadioGroup; + @VisibleForTesting + protected LinearLayout mZenRadioGroupContent; + private int MAX_MANUAL_DND_OPTIONS = 3; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + NotificationManager noMan = (NotificationManager) getContext(). + getSystemService(Context.NOTIFICATION_SERVICE); + mContext = getContext(); + mForeverId = Condition.newId(mContext).appendPath("forever").build(); + mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); + mUserId = mContext.getUserId(); + mAttached = false; + + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) + .setTitle(R.string.zen_mode_settings_turn_on_dialog_title) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int checkedId = mZenRadioGroup.getCheckedRadioButtonId(); + ConditionTag tag = getConditionTagAt(checkedId); + + if (isForever(tag.condition)) { + MetricsLogger.action(getContext(), + MetricsProto.MetricsEvent. + NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER); + } else if (isAlarm(tag.condition)) { + MetricsLogger.action(getContext(), + MetricsProto.MetricsEvent. + NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM); + } else if (isCountdown(tag.condition)) { + MetricsLogger.action(getContext(), + MetricsProto.MetricsEvent. + NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN); + } else { + wtf(TAG, "Invalid manual condition: " + tag.condition); + } + // always triggers priority-only dnd with chosen condition + noMan.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + getRealConditionId(tag.condition), TAG); + } + }); + + View contentView = getContentView(); + bindConditions(forever()); + builder.setView(contentView); + return builder.create(); + } + + private void hideAllConditions() { + final int N = mZenRadioGroupContent.getChildCount(); + for (int i = 0; i < N; i++) { + mZenRadioGroupContent.getChildAt(i).setVisibility(View.GONE); + } + } + + protected View getContentView() { + if (mActivity == null) { + mActivity = getActivity(); + } + final LayoutInflater inflater = mActivity.getLayoutInflater(); + View contentView = inflater.inflate(R.layout.zen_mode_turn_on_dialog_container, null); + ScrollView container = (ScrollView) contentView.findViewById(R.id.container); + + mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons); + mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content); + + for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) { + final View radioButton = inflater.inflate(R.layout.zen_mode_radio_button, + mZenRadioGroup, false); + mZenRadioGroup.addView(radioButton); + radioButton.setId(i); + + final View radioButtonContent = inflater.inflate(R.layout.zen_mode_condition, + mZenRadioGroupContent, false); + radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS); + mZenRadioGroupContent.addView(radioButtonContent); + } + hideAllConditions(); + return contentView; + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.NOTIFICATION_ZEN_MODE_ENABLE_DIALOG; + } + + @VisibleForTesting + protected void bind(final Condition condition, final View row, final int rowId) { + if (condition == null) throw new IllegalArgumentException("condition must not be null"); + final boolean enabled = condition.state == Condition.STATE_TRUE; + final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() : + new ConditionTag(); + row.setTag(tag); + final boolean first = tag.rb == null; + if (tag.rb == null) { + tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowId); + } + tag.condition = condition; + final Uri conditionId = getConditionId(tag.condition); + if (DEBUG) Log.d(TAG, "bind i=" + mZenRadioGroupContent.indexOfChild(row) + " first=" + + first + " condition=" + conditionId); + tag.rb.setEnabled(enabled); + tag.rb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + tag.rb.setChecked(true); + if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId); + MetricsLogger.action(mContext, + MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT); + announceConditionSelection(tag); + } + } + }); + + updateUi(tag, row, condition, enabled, rowId, conditionId); + row.setVisibility(View.VISIBLE); + } + + @VisibleForTesting + protected ConditionTag getConditionTagAt(int index) { + return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag(); + } + + @VisibleForTesting + protected void bindConditions(Condition c) { + // forever + bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX), + FOREVER_CONDITION_INDEX); + if (c == null) { + bindGenericCountdown(); + bindNextAlarm(getTimeUntilNextAlarmCondition()); + } else if (isForever(c)) { + getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true); + bindGenericCountdown(); + bindNextAlarm(getTimeUntilNextAlarmCondition()); + } else { + if (isAlarm(c)) { + bindGenericCountdown(); + bindNextAlarm(c); + getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true); + } else if (isCountdown(c)) { + bindNextAlarm(getTimeUntilNextAlarmCondition()); + bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX), + COUNTDOWN_CONDITION_INDEX); + getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true); + } else { + wtf(TAG, "Invalid manual condition: " + c); + } + } + } + + public static Uri getConditionId(Condition condition) { + return condition != null ? condition.id : null; + } + + public Condition forever() { + Uri foreverId = Condition.newId(mContext).appendPath("forever").build(); + return new Condition(foreverId, foreverSummary(mContext), "", "", 0 /*icon*/, + Condition.STATE_TRUE, 0 /*flags*/); + } + + public long getNextAlarm() { + final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock(mUserId); + return info != null ? info.getTriggerTime() : 0; + } + + @VisibleForTesting + protected boolean isAlarm(Condition c) { + return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id); + } + + @VisibleForTesting + protected boolean isCountdown(Condition c) { + return c != null && ZenModeConfig.isValidCountdownConditionId(c.id); + } + + private boolean isForever(Condition c) { + return c != null && mForeverId.equals(c.id); + } + + private Uri getRealConditionId(Condition condition) { + return isForever(condition) ? null : getConditionId(condition); + } + + private String foreverSummary(Context context) { + return context.getString(com.android.internal.R.string.zen_mode_forever); + } + + private static void setToMidnight(Calendar calendar) { + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + } + + // Returns a time condition if the next alarm is within the next week. + @VisibleForTesting + protected Condition getTimeUntilNextAlarmCondition() { + GregorianCalendar weekRange = new GregorianCalendar(); + setToMidnight(weekRange); + weekRange.add(Calendar.DATE, 6); + final long nextAlarmMs = getNextAlarm(); + if (nextAlarmMs > 0) { + GregorianCalendar nextAlarm = new GregorianCalendar(); + nextAlarm.setTimeInMillis(nextAlarmMs); + setToMidnight(nextAlarm); + + if (weekRange.compareTo(nextAlarm) >= 0) { + return ZenModeConfig.toNextAlarmCondition(mContext, nextAlarmMs, + ActivityManager.getCurrentUser()); + } + } + return null; + } + + @VisibleForTesting + protected void bindGenericCountdown() { + mBucketIndex = DEFAULT_BUCKET_INDEX; + Condition countdown = ZenModeConfig.toTimeCondition(mContext, + MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); + if (!mAttached || getConditionTagAt(COUNTDOWN_CONDITION_INDEX).condition == null) { + bind(countdown, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX), + COUNTDOWN_CONDITION_INDEX); + } + } + + private void updateUi(ConditionTag tag, View row, Condition condition, + boolean enabled, int rowId, Uri conditionId) { + if (tag.lines == null) { + tag.lines = row.findViewById(android.R.id.content); + } + if (tag.line1 == null) { + tag.line1 = (TextView) row.findViewById(android.R.id.text1); + } + + if (tag.line2 == null) { + tag.line2 = (TextView) row.findViewById(android.R.id.text2); + } + + final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1 + : condition.summary; + final String line2 = condition.line2; + tag.line1.setText(line1); + if (TextUtils.isEmpty(line2)) { + tag.line2.setVisibility(View.GONE); + } else { + tag.line2.setVisibility(View.VISIBLE); + tag.line2.setText(line2); + } + tag.lines.setEnabled(enabled); + tag.lines.setAlpha(enabled ? 1 : .4f); + + tag.lines.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + tag.rb.setChecked(true); + } + }); + + // minus button + final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); + button1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickTimeButton(row, tag, false /*down*/, rowId); + } + }); + + // plus button + final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); + button2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickTimeButton(row, tag, true /*up*/, rowId); + } + }); + + final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); + if (rowId == COUNTDOWN_CONDITION_INDEX && time > 0) { + button1.setVisibility(View.VISIBLE); + button2.setVisibility(View.VISIBLE); + if (mBucketIndex > -1) { + button1.setEnabled(mBucketIndex > 0); + button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1); + } else { + final long span = time - System.currentTimeMillis(); + button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS); + final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext, + MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser()); + button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary)); + } + + button1.setAlpha(button1.isEnabled() ? 1f : .5f); + button2.setAlpha(button2.isEnabled() ? 1f : .5f); + } else { + button1.setVisibility(View.GONE); + button2.setVisibility(View.GONE); + } + } + + @VisibleForTesting + protected void bindNextAlarm(Condition c) { + View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX); + ConditionTag tag = (ConditionTag) alarmContent.getTag(); + + if (c != null && (!mAttached || tag == null || tag.condition == null)) { + bind(c, alarmContent, COUNTDOWN_ALARM_CONDITION_INDEX); + } + + // hide the alarm radio button if there isn't a "next alarm condition" + tag = (ConditionTag) alarmContent.getTag(); + boolean showAlarm = tag != null && tag.condition != null; + mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility( + showAlarm ? View.VISIBLE : View.GONE); + alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.GONE); + } + + private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) { + MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_TIME, up); + Condition newCondition = null; + final int N = MINUTE_BUCKETS.length; + if (mBucketIndex == -1) { + // not on a known index, search for the next or prev bucket by time + final Uri conditionId = getConditionId(tag.condition); + final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); + final long now = System.currentTimeMillis(); + for (int i = 0; i < N; i++) { + int j = up ? i : N - 1 - i; + final int bucketMinutes = MINUTE_BUCKETS[j]; + final long bucketTime = now + bucketMinutes * MINUTES_MS; + if (up && bucketTime > time || !up && bucketTime < time) { + mBucketIndex = j; + newCondition = ZenModeConfig.toTimeCondition(mContext, + bucketTime, bucketMinutes, ActivityManager.getCurrentUser(), + false /*shortVersion*/); + break; + } + } + if (newCondition == null) { + mBucketIndex = DEFAULT_BUCKET_INDEX; + newCondition = ZenModeConfig.toTimeCondition(mContext, + MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); + } + } else { + // on a known index, simply increment or decrement + mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); + newCondition = ZenModeConfig.toTimeCondition(mContext, + MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); + } + bind(newCondition, row, rowId); + tag.rb.setChecked(true); + announceConditionSelection(tag); + } + + private void announceConditionSelection(ConditionTag tag) { + // condition will always be priority-only + String modeText = mContext.getString(R.string.zen_interruption_level_priority); + if (tag.line1 != null) { + mZenRadioGroupContent.announceForAccessibility(mContext.getString( + R.string.zen_mode_and_condition, modeText, tag.line1.getText())); + } + } + + // used as the view tag on condition rows + @VisibleForTesting + protected static class ConditionTag { + public RadioButton rb; + public View lines; + public TextView line1; + public TextView line2; + public Condition condition; + } +} diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 4de528e0a03..e047efa154b 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -27,12 +27,16 @@ import android.content.pm.ParceledListSlice; import android.graphics.drawable.Drawable; import android.os.ServiceManager; import android.os.UserHandle; +import android.service.notification.NotifyingApp; import android.util.IconDrawableFactory; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.Utils; +import java.util.ArrayList; +import java.util.List; + public class NotificationBackend { private static final String TAG = "NotificationBackend"; @@ -185,7 +189,6 @@ public class NotificationBackend { } } - public int getDeletedChannelCount(String pkg, int uid) { try { return sINM.getDeletedChannelCount(pkg, uid); @@ -204,6 +207,15 @@ public class NotificationBackend { } } + public List getRecentApps() { + try { + return sINM.getRecentNotifyingAppsForUser(UserHandle.myUserId()).getList(); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return new ArrayList<>(); + } + } + static class Row { public String section; } diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java index 2a7e7d55848..8b0ed463a80 100644 --- a/src/com/android/settings/notification/NotificationSettingsBase.java +++ b/src/com/android/settings/notification/NotificationSettingsBase.java @@ -168,14 +168,7 @@ abstract public class NotificationSettingsBase extends DashboardFragment { mChannel = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_ID)) ? mBackend.getChannel(mPkg, mUid, args.getString(Settings.EXTRA_CHANNEL_ID)) : null; - NotificationChannelGroup group = - (args != null && args.containsKey(Settings.EXTRA_CHANNEL_GROUP_ID)) - ? mBackend.getGroupWithChannels(mPkg, mUid, - args.getString(Settings.EXTRA_CHANNEL_GROUP_ID)) - : null; - if (group != null) { - mChannelGroup = new NotificationChannelGroupWrapper(group); - } + NotificationChannelGroup group = null; mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended( mContext, mPkg, mUserId); diff --git a/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java new file mode 100644 index 00000000000..ef34a9b65e6 --- /dev/null +++ b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java @@ -0,0 +1,293 @@ +/* + * 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.notification; + +import android.app.Application; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.service.notification.NotifyingApp; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IconDrawableFactory; +import android.util.Log; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.applications.AppInfoBase; +import com.android.settings.applications.InstalledAppCounter; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.widget.AppPreference; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.wrapper.PackageManagerWrapper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This controller displays a list of recently used apps and a "See all" button. If there is + * no recently used app, "See all" will be displayed as "Notifications". + */ +public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin { + + private static final String TAG = "RecentNotisCtrl"; + private static final String KEY_PREF_CATEGORY = "recent_notifications_category"; + @VisibleForTesting + static final String KEY_DIVIDER = "all_notifications_divider"; + @VisibleForTesting + static final String KEY_SEE_ALL = "all_notifications"; + private static final int SHOW_RECENT_APP_COUNT = 5; + private static final Set SKIP_SYSTEM_PACKAGES = new ArraySet<>(); + + private final Fragment mHost; + private final PackageManager mPm; + private final NotificationBackend mNotificationBackend; + private final int mUserId; + private final IconDrawableFactory mIconDrawableFactory; + + private List mApps; + private final ApplicationsState mApplicationsState; + + private PreferenceCategory mCategory; + private Preference mSeeAllPref; + private Preference mDivider; + private boolean mHasRecentApps; + + static { + SKIP_SYSTEM_PACKAGES.addAll(Arrays.asList( + "android", + "com.android.phone", + "com.android.settings", + "com.android.systemui", + "com.android.providers.calendar", + "com.android.providers.media" + )); + } + + public RecentNotifyingAppsPreferenceController(Context context, NotificationBackend backend, + Application app, Fragment host) { + this(context, backend, app == null ? null : ApplicationsState.getInstance(app), host); + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + RecentNotifyingAppsPreferenceController(Context context, NotificationBackend backend, + ApplicationsState appState, Fragment host) { + super(context); + mIconDrawableFactory = IconDrawableFactory.newInstance(context); + mUserId = UserHandle.myUserId(); + mPm = context.getPackageManager(); + mHost = host; + mApplicationsState = appState; + mNotificationBackend = backend; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_PREF_CATEGORY; + } + + @Override + public void updateNonIndexableKeys(List keys) { + PreferenceControllerMixin.super.updateNonIndexableKeys(keys); + // Don't index category name into search. It's not actionable. + keys.add(KEY_PREF_CATEGORY); + keys.add(KEY_DIVIDER); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + mCategory = (PreferenceCategory) screen.findPreference(getPreferenceKey()); + mSeeAllPref = screen.findPreference(KEY_SEE_ALL); + mDivider = screen.findPreference(KEY_DIVIDER); + super.displayPreference(screen); + refreshUi(mCategory.getContext()); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + refreshUi(mCategory.getContext()); + // Show total number of installed apps as See all's summary. + new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON, + new PackageManagerWrapper(mContext.getPackageManager())) { + @Override + protected void onCountComplete(int num) { + if (mHasRecentApps) { + mSeeAllPref.setTitle(mContext.getString(R.string.see_all_apps_title, num)); + } else { + mSeeAllPref.setSummary(mContext.getString(R.string.apps_summary, num)); + } + } + }.execute(); + + } + + @VisibleForTesting + void refreshUi(Context prefContext) { + reloadData(); + final List recentApps = getDisplayableRecentAppList(); + if (recentApps != null && !recentApps.isEmpty()) { + mHasRecentApps = true; + displayRecentApps(prefContext, recentApps); + } else { + mHasRecentApps = false; + displayOnlyAllAppsLink(); + } + } + + @VisibleForTesting + void reloadData() { + mApps = mNotificationBackend.getRecentApps(); + } + + private void displayOnlyAllAppsLink() { + mCategory.setTitle(null); + mDivider.setVisible(false); + mSeeAllPref.setTitle(R.string.notifications_title); + mSeeAllPref.setIcon(null); + int prefCount = mCategory.getPreferenceCount(); + for (int i = prefCount - 1; i >= 0; i--) { + final Preference pref = mCategory.getPreference(i); + if (!TextUtils.equals(pref.getKey(), KEY_SEE_ALL)) { + mCategory.removePreference(pref); + } + } + } + + private void displayRecentApps(Context prefContext, List recentApps) { + mCategory.setTitle(R.string.recent_notifications); + mDivider.setVisible(true); + mSeeAllPref.setSummary(null); + mSeeAllPref.setIcon(R.drawable.ic_chevron_right_24dp); + + // Rebind prefs/avoid adding new prefs if possible. Adding/removing prefs causes jank. + // Build a cached preference pool + final Map appPreferences = new ArrayMap<>(); + int prefCount = mCategory.getPreferenceCount(); + for (int i = 0; i < prefCount; i++) { + final Preference pref = mCategory.getPreference(i); + final String key = pref.getKey(); + if (!TextUtils.equals(key, KEY_SEE_ALL)) { + appPreferences.put(key, pref); + } + } + final int recentAppsCount = recentApps.size(); + for (int i = 0; i < recentAppsCount; i++) { + final NotifyingApp app = recentApps.get(i); + // Bind recent apps to existing prefs if possible, or create a new pref. + final String pkgName = app.getPackage(); + final ApplicationsState.AppEntry appEntry = + mApplicationsState.getEntry(app.getPackage(), mUserId); + if (appEntry == null) { + continue; + } + + boolean rebindPref = true; + Preference pref = appPreferences.remove(pkgName); + if (pref == null) { + pref = new AppPreference(prefContext); + rebindPref = false; + } + pref.setKey(pkgName); + pref.setTitle(appEntry.label); + pref.setIcon(mIconDrawableFactory.getBadgedIcon(appEntry.info)); + pref.setSummary(Utils.formatRelativeTime(mContext, + System.currentTimeMillis() - app.getLastNotified(), false)); + pref.setOrder(i); + pref.setOnPreferenceClickListener(preference -> { + AppInfoBase.startAppInfoFragment(AppNotificationSettings.class, + R.string.notifications_title, pkgName, appEntry.info.uid, mHost, + 1001 /*RequestCode */, + MetricsProto.MetricsEvent.MANAGE_APPLICATIONS_NOTIFICATIONS); + return true; + }); + if (!rebindPref) { + mCategory.addPreference(pref); + } + } + // Remove unused prefs from pref cache pool + for (Preference unusedPrefs : appPreferences.values()) { + mCategory.removePreference(unusedPrefs); + } + } + + private List getDisplayableRecentAppList() { + Collections.sort(mApps); + List displayableApps = new ArrayList<>(SHOW_RECENT_APP_COUNT); + int count = 0; + for (NotifyingApp app : mApps) { + final ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry( + app.getPackage(), mUserId); + if (appEntry == null) { + continue; + } + if (!shouldIncludePkgInRecents(app.getPackage())) { + continue; + } + displayableApps.add(app); + count++; + if (count >= SHOW_RECENT_APP_COUNT) { + break; + } + } + return displayableApps; + } + + + /** + * Whether or not the app should be included in recent list. + */ + private boolean shouldIncludePkgInRecents(String pkgName) { + if (SKIP_SYSTEM_PACKAGES.contains(pkgName)) { + Log.d(TAG, "System package, skipping " + pkgName); + return false; + } + final Intent launchIntent = new Intent().addCategory(Intent.CATEGORY_LAUNCHER) + .setPackage(pkgName); + + if (mPm.resolveActivity(launchIntent, 0) == null) { + // Not visible on launcher -> likely not a user visible app, skip if non-instant. + final ApplicationsState.AppEntry appEntry = + mApplicationsState.getEntry(pkgName, mUserId); + if (!AppUtils.isInstant(appEntry.info)) { + Log.d(TAG, "Not a user visible or instant app, skipping " + pkgName); + return false; + } + } + return true; + } +} diff --git a/src/com/android/settings/notification/ZenModeButtonPreferenceController.java b/src/com/android/settings/notification/ZenModeButtonPreferenceController.java index f5169f04500..da540f4100e 100644 --- a/src/com/android/settings/notification/ZenModeButtonPreferenceController.java +++ b/src/com/android/settings/notification/ZenModeButtonPreferenceController.java @@ -16,6 +16,7 @@ package com.android.settings.notification; +import android.app.FragmentManager; import android.content.Context; import android.provider.Settings; import android.support.v7.preference.Preference; @@ -31,12 +32,16 @@ import com.android.settingslib.core.lifecycle.Lifecycle; public class ZenModeButtonPreferenceController extends AbstractZenModePreferenceController implements PreferenceControllerMixin { + private static final String TAG = "EnableZenModeButton"; protected static final String KEY = "zen_mode_settings_button_container"; private Button mZenButtonOn; private Button mZenButtonOff; + private FragmentManager mFragment; - public ZenModeButtonPreferenceController(Context context, Lifecycle lifecycle) { + public ZenModeButtonPreferenceController(Context context, Lifecycle lifecycle, FragmentManager + fragment) { super(context, KEY, lifecycle); + mFragment = fragment; } @Override @@ -56,11 +61,8 @@ public class ZenModeButtonPreferenceController extends AbstractZenModePreference if (null == mZenButtonOn) { mZenButtonOn = (Button) ((LayoutPreference) preference) .findViewById(R.id.zen_mode_settings_turn_on_button); - mZenButtonOn.setOnClickListener(v -> { - mMetricsFeatureProvider.action(mContext, - MetricsProto.MetricsEvent.ACTION_ZEN_TOGGLE_DND_BUTTON, true); - mBackend.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); - }); + mZenButtonOn.setOnClickListener(v -> + new EnableZenModeDialog().show(mFragment, TAG)); } if (null == mZenButtonOff) { diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index 1ee20d30633..a6145c442d1 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -17,6 +17,7 @@ package com.android.settings.notification; import android.app.AutomaticZenRule; +import android.app.FragmentManager; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.Context; @@ -50,7 +51,7 @@ public class ZenModeSettings extends ZenModeSettingsBase { @Override protected List getPreferenceControllers(Context context) { - return buildPreferenceControllers(context, getLifecycle()); + return buildPreferenceControllers(context, getLifecycle(), getFragmentManager()); } @Override @@ -59,11 +60,11 @@ public class ZenModeSettings extends ZenModeSettingsBase { } private static List buildPreferenceControllers(Context context, - Lifecycle lifecycle) { + Lifecycle lifecycle, FragmentManager fragmentManager) { List controllers = new ArrayList<>(); controllers.add(new ZenModeBehaviorPreferenceController(context, lifecycle)); controllers.add(new ZenModeAutomationPreferenceController(context)); - controllers.add(new ZenModeButtonPreferenceController(context, lifecycle)); + controllers.add(new ZenModeButtonPreferenceController(context, lifecycle, fragmentManager)); controllers.add(new ZenModeSettingsFooterPreferenceController(context, lifecycle)); return controllers; } @@ -211,7 +212,7 @@ public class ZenModeSettings extends ZenModeSettingsBase { @Override public List getPreferenceControllers(Context context) { - return buildPreferenceControllers(context, null); + return buildPreferenceControllers(context, null, null); } }; } diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index dc9df8399f9..71fbaa4ecdd 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -21,6 +21,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.settings.R; +import com.android.settings.accounts.AccountFeatureProvider; import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.bluetooth.BluetoothFeatureProvider; import com.android.settings.connecteddevice.SmsMirroringFeatureProvider; @@ -109,6 +110,8 @@ public abstract class FeatureFactory { public abstract SlicesFeatureProvider getSlicesFeatureProvider(); + public abstract AccountFeatureProvider getAccountFeatureProvider(); + public static final class FactoryNotFoundException extends RuntimeException { public FactoryNotFoundException(Throwable throwable) { super("Unable to create factory. Did you misconfigure Proguard?", throwable); diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index 275ebb66865..45dc238c42d 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -23,6 +23,8 @@ import android.net.ConnectivityManager; import android.os.UserManager; import android.support.annotation.Keep; +import com.android.settings.accounts.AccountFeatureProvider; +import com.android.settings.accounts.AccountFeatureProviderImpl; import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.applications.ApplicationFeatureProviderImpl; import com.android.settings.bluetooth.BluetoothFeatureProvider; @@ -78,6 +80,7 @@ public class FeatureFactoryImpl extends FeatureFactory { private DataPlanFeatureProvider mDataPlanFeatureProvider; private SmsMirroringFeatureProvider mSmsMirroringFeatureProvider; private SlicesFeatureProvider mSlicesFeatureProvider; + private AccountFeatureProvider mAccountFeatureProvider; @Override public SupportFeatureProvider getSupportFeatureProvider(Context context) { @@ -219,4 +222,12 @@ public class FeatureFactoryImpl extends FeatureFactory { } return mSlicesFeatureProvider; } + + @Override + public AccountFeatureProvider getAccountFeatureProvider() { + if (mAccountFeatureProvider == null) { + mAccountFeatureProvider = new AccountFeatureProviderImpl(); + } + return mAccountFeatureProvider; + } } diff --git a/src/com/android/settings/search/SearchIndexableResourcesImpl.java b/src/com/android/settings/search/SearchIndexableResourcesImpl.java index 2c20703c49f..034cbd0e06b 100644 --- a/src/com/android/settings/search/SearchIndexableResourcesImpl.java +++ b/src/com/android/settings/search/SearchIndexableResourcesImpl.java @@ -21,6 +21,7 @@ import android.support.annotation.VisibleForTesting; import com.android.settings.DateTimeSettings; import com.android.settings.DisplaySettings; import com.android.settings.LegalSettings; +import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment; import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment; import com.android.settings.accessibility.MagnificationPreferenceFragment; @@ -173,6 +174,7 @@ public class SearchIndexableResourcesImpl implements SearchIndexableResources { addIndex(ZenModeAutomationSettings.class); addIndex(NightDisplaySettings.class); addIndex(SmartBatterySettings.class); + addIndex(MyDeviceInfoFragment.class); } @Override diff --git a/src/com/android/settings/security/LockdownButtonPreferenceController.java b/src/com/android/settings/security/LockdownButtonPreferenceController.java new file mode 100644 index 00000000000..89605020b39 --- /dev/null +++ b/src/com/android/settings/security/LockdownButtonPreferenceController.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.security; + +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.TwoStatePreference; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.TogglePreferenceController; + +public class LockdownButtonPreferenceController extends TogglePreferenceController { + + private static final String KEY_LOCKDOWN_ENALBED = "security_setting_lockdown_enabled"; + + private final LockPatternUtils mLockPatternUtils; + + public LockdownButtonPreferenceController(Context context) { + super(context, KEY_LOCKDOWN_ENALBED); + mLockPatternUtils = new LockPatternUtils(context); + } + + @Override + public int getAvailabilityStatus() { + if (mLockPatternUtils.isSecure(UserHandle.myUserId())) { + return BasePreferenceController.AVAILABLE; + } else { + return BasePreferenceController.DISABLED_FOR_USER; + } + } + + @Override + public boolean isChecked() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0; + } + + @Override + public boolean setChecked(boolean isChecked) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCKDOWN_IN_POWER_MENU, isChecked ? 1 : 0); + return true; + } +} diff --git a/src/com/android/settings/security/LockscreenDashboardFragment.java b/src/com/android/settings/security/LockscreenDashboardFragment.java index df4ca30d995..7054181e431 100644 --- a/src/com/android/settings/security/LockscreenDashboardFragment.java +++ b/src/com/android/settings/security/LockscreenDashboardFragment.java @@ -93,6 +93,8 @@ public class LockscreenDashboardFragment extends DashboardFragment mOwnerInfoPreferenceController = new OwnerInfoPreferenceController(context, this, lifecycle); controllers.add(mOwnerInfoPreferenceController); + controllers.add(new LockdownButtonPreferenceController(context)); + return controllers; } @@ -122,6 +124,7 @@ public class LockscreenDashboardFragment extends DashboardFragment KEY_ADD_USER_FROM_LOCK_SCREEN)); controllers.add(new OwnerInfoPreferenceController( context, null /* fragment */, null /* lifecycle */)); + controllers.add(new LockdownButtonPreferenceController(context)); return controllers; } diff --git a/src/com/android/settings/widget/AppCheckBoxPreference.java b/src/com/android/settings/widget/AppCheckBoxPreference.java new file mode 100644 index 00000000000..9cb1d78163d --- /dev/null +++ b/src/com/android/settings/widget/AppCheckBoxPreference.java @@ -0,0 +1,38 @@ +/* + * 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.widget; + +import android.content.Context; +import android.support.v7.preference.CheckBoxPreference; +import android.util.AttributeSet; + +import com.android.settings.R; + +/** + * {@link CheckBoxPreference} that used only to display app + */ +public class AppCheckBoxPreference extends CheckBoxPreference { + public AppCheckBoxPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.preference_app); + } + + public AppCheckBoxPreference(Context context) { + super(context); + setLayoutResource(R.layout.preference_app); + } +} diff --git a/src/com/android/settings/widget/SettingsAppWidgetProvider.java b/src/com/android/settings/widget/SettingsAppWidgetProvider.java index 7dacaf5f2aa..5ccfc1b585f 100644 --- a/src/com/android/settings/widget/SettingsAppWidgetProvider.java +++ b/src/com/android/settings/widget/SettingsAppWidgetProvider.java @@ -16,6 +16,7 @@ package com.android.settings.widget; +import android.app.ActivityManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; @@ -33,10 +34,12 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.IPowerManager; import android.os.PowerManager; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserManager; import android.provider.Settings; +import android.provider.Settings.Secure; import android.util.Log; import android.widget.RemoteViews; @@ -561,27 +564,14 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) { - int currentMode = Settings.Secure.getInt(resolver, - Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); - int mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; - switch (currentMode) { - case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: - mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING; - break; - case Settings.Secure.LOCATION_MODE_BATTERY_SAVING: - mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; - break; - case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: - mode = Settings.Secure.LOCATION_MODE_OFF; - break; - case Settings.Secure.LOCATION_MODE_OFF: - mode = Settings.Secure.LOCATION_MODE_PREVIOUS; - break; - } - Settings.Secure.putInt(resolver, Settings.Secure.LOCATION_MODE, mode); - return mode != Settings.Secure.LOCATION_MODE_OFF; + LocationManager lm = + (LocationManager) context.getSystemService( + Context.LOCATION_SERVICE); + boolean currentLocationEnabled = lm.isLocationEnabled(); + lm.setLocationEnabledForUser( + !currentLocationEnabled, Process.myUserHandle()); + return lm.isLocationEnabled(); } - return getActualState(context) == STATE_ENABLED; } diff --git a/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceController.java index 1e299abc2e3..4c47a0d02a3 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.wifi.tether; import static android.net.wifi.WifiConfiguration.AP_BAND_2GHZ; import static android.net.wifi.WifiConfiguration.AP_BAND_5GHZ; +import static android.net.wifi.WifiConfiguration.AP_BAND_ANY; import android.content.Context; import android.net.wifi.WifiConfiguration; @@ -32,7 +33,8 @@ public class WifiTetherApBandPreferenceController extends WifiTetherBasePreferen private static final String TAG = "WifiTetherApBandPref"; private static final String PREF_KEY = "wifi_tether_network_ap_band"; private static final String[] BAND_VALUES = - {String.valueOf(AP_BAND_2GHZ), String.valueOf(AP_BAND_5GHZ)}; + {String.valueOf(AP_BAND_ANY), String.valueOf(AP_BAND_2GHZ), + String.valueOf(AP_BAND_5GHZ)}; private final String[] mBandEntries; private int mBandIndex; @@ -65,7 +67,7 @@ public class WifiTetherApBandPreferenceController extends WifiTetherBasePreferen } else { preference.setEntries(mBandEntries); preference.setEntryValues(BAND_VALUES); - preference.setSummary(mBandEntries[mBandIndex]); + preference.setSummary(mBandEntries[mBandIndex + 1]); preference.setValue(String.valueOf(mBandIndex)); } } @@ -78,7 +80,8 @@ public class WifiTetherApBandPreferenceController extends WifiTetherBasePreferen @Override public boolean onPreferenceChange(Preference preference, Object newValue) { mBandIndex = Integer.parseInt((String) newValue); - preference.setSummary(mBandEntries[mBandIndex]); + Log.d(TAG, "Band preference changed, updating band index to " + mBandIndex); + preference.setSummary(mBandEntries[mBandIndex + 1]); mListener.onTetherConfigUpdated(); return true; } diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index a3205f33f29..f437f966b32 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -54,4 +54,5 @@ false false false + false diff --git a/tests/robotests/res/values/config.xml b/tests/robotests/res/values/config.xml index 4004106cc9d..359df6cc0a5 100644 --- a/tests/robotests/res/values/config.xml +++ b/tests/robotests/res/values/config.xml @@ -21,4 +21,5 @@ true true true + true \ No newline at end of file diff --git a/tests/robotests/src/android/content/om/IOverlayManager.java b/tests/robotests/src/android/content/om/IOverlayManager.java index cc1d0efa791..8a895e7afc4 100644 --- a/tests/robotests/src/android/content/om/IOverlayManager.java +++ b/tests/robotests/src/android/content/om/IOverlayManager.java @@ -16,10 +16,15 @@ package android.content.om; import android.os.IBinder; +import java.util.ArrayList; +import java.util.LinkedList; + public interface IOverlayManager { public OverlayInfo getOverlayInfo(String packageName, int userId); + public java.util.List getOverlayInfosForTarget(java.lang.String targetPackageName, int userId); + public boolean setEnabled(java.lang.String packageName, boolean enable, int userId); public static class Stub { diff --git a/tests/robotests/src/android/content/om/OverlayInfo.java b/tests/robotests/src/android/content/om/OverlayInfo.java index 98ce0910f09..fb7fef1d141 100644 --- a/tests/robotests/src/android/content/om/OverlayInfo.java +++ b/tests/robotests/src/android/content/om/OverlayInfo.java @@ -14,8 +14,17 @@ package android.content.om; +import android.annotation.NonNull; + public class OverlayInfo { + public final String packageName; + + public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName, + @NonNull String baseCodePath, int state, int userId) { + this.packageName = packageName; + } + public boolean isEnabled() { return false; } diff --git a/tests/robotests/src/android/service/notification/NotifyingApp.java b/tests/robotests/src/android/service/notification/NotifyingApp.java new file mode 100644 index 00000000000..f36069b20c2 --- /dev/null +++ b/tests/robotests/src/android/service/notification/NotifyingApp.java @@ -0,0 +1,112 @@ +/* + * 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 android.service.notification; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Stub implementation of framework's NotifyingApp for Robolectric tests. Otherwise Robolectric + * throws ClassNotFoundError. + * + * TODO: Remove this class when Robolectric supports P + */ +public final class NotifyingApp implements Comparable { + + private int mUid; + private String mPkg; + private long mLastNotified; + + public NotifyingApp() {} + + public int getUid() { + return mUid; + } + + /** + * Sets the uid of the package that sent the notification. Returns self. + */ + public NotifyingApp setUid(int mUid) { + this.mUid = mUid; + return this; + } + + public String getPackage() { + return mPkg; + } + + /** + * Sets the package that sent the notification. Returns self. + */ + public NotifyingApp setPackage(@NonNull String mPkg) { + this.mPkg = mPkg; + return this; + } + + public long getLastNotified() { + return mLastNotified; + } + + /** + * Sets the time the notification was originally sent. Returns self. + */ + public NotifyingApp setLastNotified(long mLastNotified) { + this.mLastNotified = mLastNotified; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NotifyingApp that = (NotifyingApp) o; + return getUid() == that.getUid() + && getLastNotified() == that.getLastNotified() + && Objects.equals(mPkg, that.mPkg); + } + + @Override + public int hashCode() { + return Objects.hash(getUid(), mPkg, getLastNotified()); + } + + /** + * Sorts notifying apps from newest last notified date to oldest. + */ + @Override + public int compareTo(NotifyingApp o) { + if (getLastNotified() == o.getLastNotified()) { + if (getUid() == o.getUid()) { + return getPackage().compareTo(o.getPackage()); + } + return Integer.compare(getUid(), o.getUid()); + } + + return -Long.compare(getLastNotified(), o.getLastNotified()); + } + + @Override + public String toString() { + return "NotifyingApp{" + + "mUid=" + mUid + + ", mPkg='" + mPkg + '\'' + + ", mLastNotified=" + mLastNotified + + '}'; + } +} diff --git a/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java index c9841f698ec..a6af6d6cb39 100644 --- a/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/EmulateDisplayCutoutPreferenceControllerTest.java @@ -16,6 +16,9 @@ package com.android.settings.development; +import static com.android.settings.development.EmulateDisplayCutoutPreferenceController + .EMULATION_OVERLAY_PREFIX; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -29,7 +32,8 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; -import android.support.v7.preference.TwoStatePreference; +import android.content.pm.PackageManager; +import android.support.v7.preference.ListPreference; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -41,78 +45,95 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; +import java.util.Arrays; + @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class EmulateDisplayCutoutPreferenceControllerTest { + static final OverlayInfo ONE_DISABLED = + new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".one", false); + static final OverlayInfo ONE_ENABLED = + new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".one", true); + static final OverlayInfo TWO_DISABLED = + new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".two", false); + static final OverlayInfo TWO_ENABLED = + new FakeOverlay(EMULATION_OVERLAY_PREFIX + ".two", true); + @Mock Context mContext; @Mock IOverlayManager mOverlayManager; - @Mock TwoStatePreference mPreference; + @Mock PackageManager mPackageManager; + @Mock ListPreference mPreference; EmulateDisplayCutoutPreferenceController mController; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED); - mController = new EmulateDisplayCutoutPreferenceController(mContext, mOverlayManager); + mockCurrentOverlays(); + when(mPackageManager.getApplicationInfo(any(), anyInt())).thenThrow( + PackageManager.NameNotFoundException.class); + mController = createController(); mController.setPreference(mPreference); } + Object mockCurrentOverlays(OverlayInfo... overlays) { + return when(mOverlayManager.getOverlayInfosForTarget(eq("android"), anyInt())) + .thenReturn(Arrays.asList(overlays)); + } + @Test public void isAvailable_true() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED); + mockCurrentOverlays(ONE_DISABLED, TWO_DISABLED); - assertThat(new EmulateDisplayCutoutPreferenceController(mContext, mOverlayManager) - .isAvailable()).isTrue(); + assertThat(createController().isAvailable()).isTrue(); } @Test public void isAvailable_false() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(null); + mockCurrentOverlays(); - assertThat(new EmulateDisplayCutoutPreferenceController(mContext, mOverlayManager) - .isAvailable()).isFalse(); + assertThat(createController().isAvailable()).isFalse(); } @Test public void onPreferenceChange_enable() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED); + mockCurrentOverlays(ONE_DISABLED, TWO_DISABLED); - mController.onPreferenceChange(null, true); + mController.onPreferenceChange(null, TWO_DISABLED.packageName); - verify(mOverlayManager).setEnabled(any(), eq(true), anyInt()); + verify(mOverlayManager).setEnabled(eq(TWO_DISABLED.packageName), eq(true), anyInt()); } @Test public void onPreferenceChange_disable() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(ENABLED); + mockCurrentOverlays(ONE_DISABLED, TWO_ENABLED); - mController.onPreferenceChange(null, false); + mController.onPreferenceChange(null, ""); - verify(mOverlayManager).setEnabled(any(), eq(false), anyInt()); + verify(mOverlayManager).setEnabled(eq(TWO_ENABLED.packageName), eq(false), anyInt()); } @Test public void updateState_enabled() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(ENABLED); + mockCurrentOverlays(ONE_DISABLED, TWO_ENABLED); mController.updateState(null); - verify(mPreference).setChecked(true); + verify(mPreference).setValueIndex(2); } @Test public void updateState_disabled() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED); + mockCurrentOverlays(ONE_DISABLED, TWO_DISABLED); mController.updateState(null); - verify(mPreference).setChecked(false); + verify(mPreference).setValueIndex(0); } @Test public void onDeveloperOptionsSwitchEnabled() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(DISABLED); + mockCurrentOverlays(); mController.onDeveloperOptionsSwitchEnabled(); @@ -122,27 +143,30 @@ public class EmulateDisplayCutoutPreferenceControllerTest { @Test public void onDeveloperOptionsSwitchDisabled() throws Exception { - when(mOverlayManager.getOverlayInfo(any(), anyInt())).thenReturn(ENABLED); + mockCurrentOverlays(ONE_ENABLED, TWO_DISABLED); mController.onDeveloperOptionsSwitchDisabled(); verify(mPreference).setEnabled(false); - verify(mPreference).setChecked(false); - verify(mOverlayManager).setEnabled(any(), eq(false), anyInt()); + verify(mOverlayManager).setEnabled(eq(ONE_ENABLED.packageName), eq(false), anyInt()); } - static final OverlayInfo ENABLED = new OverlayInfo() { + private EmulateDisplayCutoutPreferenceController createController() { + return new EmulateDisplayCutoutPreferenceController(mContext, mPackageManager, + mOverlayManager); + } + + private static class FakeOverlay extends OverlayInfo { + private final boolean mEnabled; + + public FakeOverlay(String pkg, boolean enabled) { + super(pkg, "android", "/", 0, 0); + mEnabled = enabled; + } + @Override public boolean isEnabled() { - return true; + return mEnabled; } - }; - - static final OverlayInfo DISABLED = new OverlayInfo() { - @Override - public boolean isEnabled() { - return false; - } - }; - + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceControllerTest.java new file mode 100644 index 00000000000..9a80c5c5efd --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceControllerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.development; + +import static com.android.settings.development.WifiConnectedMacRandomizationPreferenceController + .SETTING_VALUE_OFF; +import static com.android.settings.development.WifiConnectedMacRandomizationPreferenceController + .SETTING_VALUE_ON; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +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.annotation.Config; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class WifiConnectedMacRandomizationPreferenceControllerTest { + + @Mock + private PreferenceScreen mPreferenceScreen; + + private Context mContext; + private SwitchPreference mPreference; + private WifiConnectedMacRandomizationPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new WifiConnectedMacRandomizationPreferenceController(mContext); + mPreference = new SwitchPreference(mContext); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( + mPreference); + mController.displayPreference(mPreferenceScreen); + } + + @Test + public void isAvailable_trueSupportFlag_shouldReturnTrue() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + @Config(qualifiers = "mcc999") + public void isAvailable_falseSupportFlag_shouldReturnFalse() { + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void onPreferenceChange_settingEnabled_shouldEnableConnectedMacRandomization() { + mController.onPreferenceChange(mPreference, true /* new value */); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, -1 /* default */); + + assertThat(mode).isEqualTo(SETTING_VALUE_ON); + } + + @Test + public void onPreferenceChange_settingDisabled_shouldDisableConnectedMacRandomization() { + mController.onPreferenceChange(mPreference, false /* new value */); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, -1 /* default */); + + assertThat(mode).isEqualTo(SETTING_VALUE_OFF); + } + + @Test + public void updateState_settingEnabled_shouldEnablePreference() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, SETTING_VALUE_ON); + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void updateState_settingDisabled_shouldDisablePreference() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, SETTING_VALUE_OFF); + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void onDeveloperOptionsSwitchEnabled_shouldEnablePreference() { + mController.onDeveloperOptionsSwitchEnabled(); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() { + mController.onDeveloperOptionsSwitchDisabled(); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, -1 /* default */); + + assertThat(mode).isEqualTo(SETTING_VALUE_OFF); + assertThat(mPreference.isChecked()).isFalse(); + assertThat(mPreference.isEnabled()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/BrandedAccountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/BrandedAccountPreferenceControllerTest.java new file mode 100644 index 00000000000..521800b57fa --- /dev/null +++ b/tests/robotests/src/com/android/settings/deviceinfo/BrandedAccountPreferenceControllerTest.java @@ -0,0 +1,67 @@ +/* + * 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.deviceinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.accounts.Account; +import android.content.Context; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BrandedAccountPreferenceControllerTest { + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + private BrandedAccountPreferenceController mController; + private FakeFeatureFactory fakeFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + fakeFeatureFactory = FakeFeatureFactory.setupForTest(); + mController = new BrandedAccountPreferenceController(mContext); + } + + @Test + public void isAvailable_defaultOff() { + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_onWhenAccountIsAvailable() { + when(fakeFeatureFactory.mAccountFeatureProvider.getAccounts(any(Context.class))).thenReturn( + new Account[] + {new Account("fake@account.foo", "fake.reallyfake")}); + mController = new BrandedAccountPreferenceController(mContext); + assertThat(mController.isAvailable()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/MyDeviceInfoFragmentTest.java b/tests/robotests/src/com/android/settings/deviceinfo/MyDeviceInfoFragmentTest.java new file mode 100644 index 00000000000..36f0662c2a8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/deviceinfo/MyDeviceInfoFragmentTest.java @@ -0,0 +1,100 @@ +/* + * 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.deviceinfo; + +import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.v7.preference.PreferenceScreen; +import android.telephony.TelephonyManager; + +import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment; +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; +import com.android.settings.testutils.shadow.ShadowConnectivityManager; +import com.android.settings.testutils.shadow.ShadowUserManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = {ShadowConnectivityManager.class, ShadowUserManager.class} +) +public class MyDeviceInfoFragmentTest { + @Mock + private Activity mActivity; + @Mock + private PreferenceScreen mScreen; + @Mock + private TelephonyManager mTelephonyManager; + + private Context mContext; + private MyDeviceInfoFragment mSettings; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(); + mContext = RuntimeEnvironment.application; + mSettings = spy(new MyDeviceInfoFragment()); + + when(mSettings.getActivity()).thenReturn(mActivity); + when(mSettings.getContext()).thenReturn(mContext); + when(mActivity.getTheme()).thenReturn(mContext.getTheme()); + when(mActivity.getResources()).thenReturn(mContext.getResources()); + doNothing().when(mSettings).onCreatePreferences(any(), any()); + + doReturn(mScreen).when(mSettings).getPreferenceScreen(); + when(mSettings.getPreferenceScreen()).thenReturn(mScreen); + ShadowApplication.getInstance().setSystemService(Context.TELEPHONY_SERVICE, + mTelephonyManager); + } + + @Test + @Config(shadows = {SettingsShadowResources.SettingsShadowTheme.class, + SettingsShadowSystemProperties.class}) + public void onCreate_fromSearch_shouldNotOverrideInitialExpandedCount() { + final Bundle args = new Bundle(); + args.putString(EXTRA_FRAGMENT_ARG_KEY, "search_key"); + mSettings.setArguments(args); + + mSettings.onCreate(null /* icicle */); + + verify(mScreen).setInitialExpandedChildrenCount(Integer.MAX_VALUE); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java index a9de06176b4..521ead4d337 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/RestrictedAppDetailsTest.java @@ -28,6 +28,7 @@ import android.content.pm.PackageManager; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceManager; +import android.util.IconDrawableFactory; import com.android.settings.TestConfig; @@ -53,6 +54,8 @@ public class RestrictedAppDetailsTest { private PackageManager mPackageManager; @Mock private ApplicationInfo mApplicationInfo; + @Mock + private IconDrawableFactory mIconDrawableFactory; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PreferenceManager mPreferenceManager; private RestrictedAppDetails mFragment; @@ -68,6 +71,7 @@ public class RestrictedAppDetailsTest { doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); doReturn(mContext).when(mPreferenceManager).getContext(); mFragment.mPackageManager = mPackageManager; + mFragment.mIconDrawableFactory = mIconDrawableFactory; mFragment.mPackageOpsList = new ArrayList<>(); mFragment.mPackageOpsList.add( new AppOpsManager.PackageOps(PACKAGE_NAME, UID, null /* entries */)); diff --git a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java index f456f4109e5..8cc92cdf43f 100644 --- a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java @@ -43,6 +43,7 @@ import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowSecureSettings; import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.wrapper.LocationManagerWrapper; import java.util.ArrayList; import java.util.List; import org.junit.Before; @@ -53,11 +54,15 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, - shadows = {ShadowSecureSettings.class}) + shadows = { + ShadowSecureSettings.class, + LocationEnablerTest.ShadowLocationManagerWrapper.class}) public class LocationEnablerTest { @Mock @@ -124,7 +129,7 @@ public class LocationEnablerTest { } @Test - public void isEnabled_locationONotRestricted_shouldReturnTrue() { + public void isEnabled_locationNotRestricted_shouldReturnTrue() { when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); assertThat(mEnabler.isEnabled(Settings.Secure.LOCATION_MODE_BATTERY_SAVING)).isTrue(); @@ -178,14 +183,35 @@ public class LocationEnablerTest { when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_BATTERY_SAVING); - mEnabler.setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); verify(mContext).sendBroadcastAsUser( argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)), eq(UserHandle.of(ActivityManager.getCurrentUser())), eq(WRITE_SECURE_SETTINGS)); + } + @Test + public void setLocationEnabled_notRestricted_shouldRefreshLocation() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + mEnabler.setLocationEnabled(true); + + verify(mEnabler).refreshLocationMode(); + } + + @Test + public void setLocationEnabled_notRestricted_shouldBroadcastUpdate() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + mEnabler.setLocationEnabled(true); + + verify(mContext).sendBroadcastAsUser( + argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)), + eq(UserHandle.of(ActivityManager.getCurrentUser())), + eq(WRITE_SECURE_SETTINGS)); } @Test @@ -241,5 +267,12 @@ public class LocationEnablerTest { return intent -> TextUtils.equals(expected, intent.getAction()); } + @Implements(value = LocationManagerWrapper.class) + public static class ShadowLocationManagerWrapper { + @Implementation + public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) { + // Do nothing + } + } } diff --git a/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java index 6d824acca8a..4410d6f2c47 100644 --- a/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java @@ -88,18 +88,17 @@ public class LocationSwitchBarControllerTest { } @Test - public void onSwitchChanged_switchChecked_shouldSetPreviousLocationMode() { + public void onSwitchChanged_switchChecked_shouldSetLocationEnabled() { mController.onSwitchChanged(mSwitch, true); - verify(mEnabler).setLocationMode( - android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS); + verify(mEnabler).setLocationEnabled(true); } @Test - public void onSwitchChanged_switchUnchecked_shouldSetLocationModeOff() { + public void onSwitchChanged_switchUnchecked_shouldSetLocationDisabled() { mController.onSwitchChanged(mSwitch, false); - verify(mEnabler).setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF); + verify(mEnabler).setLocationEnabled(false); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/EnableZenModeDialogTest.java b/tests/robotests/src/com/android/settings/notification/EnableZenModeDialogTest.java new file mode 100644 index 00000000000..8b5ef79dcbc --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/EnableZenModeDialogTest.java @@ -0,0 +1,152 @@ +/* + * 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.notification; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.net.Uri; +import android.service.notification.Condition; +import android.view.LayoutInflater; + +import com.android.settings.TestConfig; +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; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class EnableZenModeDialogTest { + private EnableZenModeDialog mController; + + @Mock + private Context mContext; + @Mock + private Activity mActivity; + @Mock + private Fragment mFragment; + + private Context mShadowContext; + private LayoutInflater mLayoutInflater; + private Condition mCountdownCondition; + private Condition mAlarmCondition; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mShadowContext = RuntimeEnvironment.application; + when(mActivity.getApplicationContext()).thenReturn(mShadowContext); + when(mContext.getApplicationContext()).thenReturn(mContext); + when(mFragment.getContext()).thenReturn(mShadowContext); + mLayoutInflater = LayoutInflater.from(mShadowContext); + when(mActivity.getLayoutInflater()).thenReturn(mLayoutInflater); + + mController = spy(new EnableZenModeDialog()); + mController.mContext = mContext; + mController.mActivity = mActivity; + mController.mForeverId = Condition.newId(mContext).appendPath("forever").build(); + when(mContext.getString(com.android.internal.R.string.zen_mode_forever)) + .thenReturn("testSummary"); + mController.getContentView(); + + // these methods use static calls to ZenModeConfig which would normally fail in robotests, + // so instead do nothing: + doNothing().when(mController).bindGenericCountdown(); + doReturn(null).when(mController).getTimeUntilNextAlarmCondition(); + doNothing().when(mController).bindNextAlarm(any()); + + // as a result of doing nothing above, must bind manually: + Uri alarm = Condition.newId(mContext).appendPath("alarm").build(); + mAlarmCondition = new Condition(alarm, "alarm", "", "", 0, 0, 0); + Uri countdown = Condition.newId(mContext).appendPath("countdown").build(); + mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0); + mController.bind(mCountdownCondition, + mController.mZenRadioGroupContent.getChildAt( + EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX), + EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX); + mController.bind(mAlarmCondition, + mController.mZenRadioGroupContent.getChildAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX), + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX); + } + + @Test + public void testForeverChecked() { + mController.bindConditions(mController.forever()); + + assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testNoneChecked() { + mController.bindConditions(null); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testAlarmChecked() { + doReturn(false).when(mController).isCountdown(mAlarmCondition); + doReturn(true).when(mController).isAlarm(mAlarmCondition); + + mController.bindConditions(mAlarmCondition); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertTrue(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } + + @Test + public void testCountdownChecked() { + doReturn(false).when(mController).isAlarm(mCountdownCondition); + doReturn(true).when(mController).isCountdown(mCountdownCondition); + + mController.bindConditions(mCountdownCondition); + assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb + .isChecked()); + assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb + .isChecked()); + assertFalse(mController.getConditionTagAt( + EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked()); + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java new file mode 100644 index 00000000000..a25bb002502 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java @@ -0,0 +1,301 @@ +/* + * 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.notification; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +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.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.service.notification.NotifyingApp; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.instantapps.InstantAppDataProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class RecentNotifyingAppsPreferenceControllerTest { + + @Mock + private PreferenceScreen mScreen; + @Mock + private PreferenceCategory mCategory; + @Mock + private Preference mSeeAllPref; + @Mock + private PreferenceCategory mDivider; + @Mock + private UserManager mUserManager; + @Mock + private ApplicationsState mAppState; + @Mock + private PackageManager mPackageManager; + @Mock + private ApplicationsState.AppEntry mAppEntry; + @Mock + private ApplicationInfo mApplicationInfo; + @Mock + private NotificationBackend mBackend; + + private Context mContext; + private RecentNotifyingAppsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE); + doReturn(mPackageManager).when(mContext).getPackageManager(); + + mController = new RecentNotifyingAppsPreferenceController( + mContext, mBackend, mAppState, null); + when(mScreen.findPreference(anyString())).thenReturn(mCategory); + + when(mScreen.findPreference(RecentNotifyingAppsPreferenceController.KEY_SEE_ALL)) + .thenReturn(mSeeAllPref); + when(mScreen.findPreference(RecentNotifyingAppsPreferenceController.KEY_DIVIDER)) + .thenReturn(mDivider); + when(mCategory.getContext()).thenReturn(mContext); + } + + @Test + public void isAlwaysAvailable() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void doNotIndexCategory() { + final List nonIndexable = new ArrayList<>(); + + mController.updateNonIndexableKeys(nonIndexable); + + assertThat(nonIndexable).containsAllOf(mController.getPreferenceKey(), + RecentNotifyingAppsPreferenceController.KEY_DIVIDER); + } + + @Test + public void onDisplayAndUpdateState_shouldRefreshUi() { + mController = spy(new RecentNotifyingAppsPreferenceController( + mContext, null, (ApplicationsState) null, null)); + + doNothing().when(mController).refreshUi(mContext); + + mController.displayPreference(mScreen); + mController.updateState(mCategory); + + verify(mController, times(2)).refreshUi(mContext); + } + + @Test + @Config(qualifiers = "mcc999") + public void display_shouldNotShowRecents_showAppInfoPreference() { + mController.displayPreference(mScreen); + + verify(mCategory, never()).addPreference(any(Preference.class)); + verify(mCategory).setTitle(null); + verify(mSeeAllPref).setTitle(R.string.notifications_title); + verify(mSeeAllPref).setIcon(null); + verify(mDivider).setVisible(false); + } + + @Test + public void display_showRecents() { + final List apps = new ArrayList<>(); + final NotifyingApp app1 = new NotifyingApp() + .setPackage("pkg.class") + .setLastNotified(System.currentTimeMillis()); + final NotifyingApp app2 = new NotifyingApp() + .setLastNotified(System.currentTimeMillis()) + .setPackage("com.android.settings"); + final NotifyingApp app3 = new NotifyingApp() + .setLastNotified(System.currentTimeMillis() - 1000) + .setPackage("pkg.class2"); + + apps.add(app1); + apps.add(app2); + apps.add(app3); + + // app1, app2 are valid apps. app3 is invalid. + when(mAppState.getEntry(app1.getPackage(), UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(app2.getPackage(), UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(app3.getPackage(), UserHandle.myUserId())) + .thenReturn(null); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn( + new ResolveInfo()); + when(mBackend.getRecentApps()).thenReturn(apps); + mAppEntry.info = mApplicationInfo; + + mController.displayPreference(mScreen); + + verify(mCategory).setTitle(R.string.recent_notifications); + // Only add app1. app2 is skipped because of the package name, app3 skipped because + // it's invalid app. + verify(mCategory, times(1)).addPreference(any(Preference.class)); + + verify(mSeeAllPref).setSummary(null); + verify(mSeeAllPref).setIcon(R.drawable.ic_chevron_right_24dp); + verify(mDivider).setVisible(true); + } + + @Test + public void display_showRecentsWithInstantApp() { + // Regular app. + final List apps = new ArrayList<>(); + final NotifyingApp app1 = new NotifyingApp(). + setLastNotified(System.currentTimeMillis()) + .setPackage("com.foo.bar"); + apps.add(app1); + + // Instant app. + final NotifyingApp app2 = new NotifyingApp() + .setLastNotified(System.currentTimeMillis() + 200) + .setPackage("com.foo.barinstant"); + apps.add(app2); + + ApplicationsState.AppEntry app1Entry = mock(ApplicationsState.AppEntry.class); + ApplicationsState.AppEntry app2Entry = mock(ApplicationsState.AppEntry.class); + app1Entry.info = mApplicationInfo; + app2Entry.info = mApplicationInfo; + + when(mAppState.getEntry(app1.getPackage(), UserHandle.myUserId())).thenReturn(app1Entry); + when(mAppState.getEntry(app2.getPackage(), UserHandle.myUserId())).thenReturn(app2Entry); + + // Only the regular app app1 should have its intent resolve. + when(mPackageManager.resolveActivity(argThat(intentMatcher(app1.getPackage())), + anyInt())).thenReturn(new ResolveInfo()); + + when(mBackend.getRecentApps()).thenReturn(apps); + + // Make sure app2 is considered an instant app. + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (ApplicationInfo info) -> { + if (info == app2Entry.info) { + return true; + } else { + return false; + } + }); + + mController.displayPreference(mScreen); + + ArgumentCaptor prefCaptor = ArgumentCaptor.forClass(Preference.class); + verify(mCategory, times(2)).addPreference(prefCaptor.capture()); + List prefs = prefCaptor.getAllValues(); + assertThat(prefs.get(1).getKey()).isEqualTo(app1.getPackage()); + assertThat(prefs.get(0).getKey()).isEqualTo(app2.getPackage()); + } + + @Test + public void display_hasRecentButNoneDisplayable_showAppInfo() { + final List apps = new ArrayList<>(); + final NotifyingApp app1 = new NotifyingApp() + .setPackage("com.android.phone") + .setLastNotified(System.currentTimeMillis()); + final NotifyingApp app2 = new NotifyingApp() + .setPackage("com.android.settings") + .setLastNotified(System.currentTimeMillis()); + apps.add(app1); + apps.add(app2); + + // app1, app2 are not displayable + when(mAppState.getEntry(app1.getPackage(), UserHandle.myUserId())) + .thenReturn(mock(ApplicationsState.AppEntry.class)); + when(mAppState.getEntry(app2.getPackage(), UserHandle.myUserId())) + .thenReturn(mock(ApplicationsState.AppEntry.class)); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn( + new ResolveInfo()); + when(mBackend.getRecentApps()).thenReturn(apps); + + mController.displayPreference(mScreen); + + verify(mCategory, never()).addPreference(any(Preference.class)); + verify(mCategory).setTitle(null); + verify(mSeeAllPref).setTitle(R.string.notifications_title); + verify(mSeeAllPref).setIcon(null); + } + + @Test + public void display_showRecents_formatSummary() { + final List apps = new ArrayList<>(); + final NotifyingApp app1 = new NotifyingApp() + .setLastNotified(System.currentTimeMillis()) + .setPackage("pkg.class"); + apps.add(app1); + + when(mAppState.getEntry(app1.getPackage(), UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn( + new ResolveInfo()); + when(mBackend.getRecentApps()).thenReturn(apps); + mAppEntry.info = mApplicationInfo; + + mController.displayPreference(mScreen); + + verify(mCategory).addPreference(argThat(summaryMatches("0 min. ago"))); + } + + private static ArgumentMatcher summaryMatches(String expected) { + return preference -> TextUtils.equals(expected, preference.getSummary()); + } + + // Used for matching an intent with a specific package name. + private static ArgumentMatcher intentMatcher(String packageName) { + return intent -> packageName.equals(intent.getPackage()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeButtonPreferenceControllerTest.java index 862b8d06afe..ed42890b3e3 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeButtonPreferenceControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.FragmentManager; import android.app.NotificationManager; import android.content.ContentResolver; import android.content.Context; @@ -79,7 +80,8 @@ public class ZenModeButtonPreferenceControllerTest { mContext = shadowApplication.getApplicationContext(); mContentResolver = RuntimeEnvironment.application.getContentResolver(); - mController = new ZenModeButtonPreferenceController(mContext, mock(Lifecycle.class)); + mController = new ZenModeButtonPreferenceController(mContext, mock(Lifecycle.class), + mock(FragmentManager.class)); when(mNotificationManager.getNotificationPolicy()).thenReturn(mPolicy); ReflectionHelpers.setField(mController, "mBackend", mBackend); ReflectionHelpers.setField(mController, "mZenButtonOn", mZenButtonOn); diff --git a/tests/robotests/src/com/android/settings/security/LockdownButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/LockdownButtonPreferenceControllerTest.java new file mode 100644 index 00000000000..7738f863e62 --- /dev/null +++ b/tests/robotests/src/com/android/settings/security/LockdownButtonPreferenceControllerTest.java @@ -0,0 +1,98 @@ +/* + * 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.security; + +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.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.TestConfig; +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 org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class LockdownButtonPreferenceControllerTest { + @Mock + private LockPatternUtils mLockPatternUtils; + private SwitchPreference mPreference; + + private Context mContext; + private LockdownButtonPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mPreference = new SwitchPreference(mContext); + + mController = spy(new LockdownButtonPreferenceController(mContext)); + ReflectionHelpers.setField(mController, "mLockPatternUtils", mLockPatternUtils); + } + + @Test + public void isAvailable_lockSet_shouldReturnTrue() throws Exception { + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_lockUnset_shouldReturnFalse() throws Exception { + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void onPreferenceChange_settingIsUpdated() throws Exception { + boolean state = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0; + assertThat(mController.onPreferenceChange(mPreference, !state)).isTrue(); + boolean newState = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0; + assertThat(newState).isEqualTo(!state); + } + + @Test + public void onSettingChange_preferenceIsUpdated() throws Exception { + boolean state = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0; + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isEqualTo(state); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCKDOWN_IN_POWER_MENU, state ? 0 : 1); + + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isEqualTo(!state); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index 752ca1e21dc..ad51f79cab2 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.when; import android.content.Context; +import com.android.settings.accounts.AccountFeatureProvider; import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.bluetooth.BluetoothFeatureProvider; import com.android.settings.connecteddevice.SmsMirroringFeatureProvider; @@ -65,6 +66,7 @@ public class FakeFeatureFactory extends FeatureFactory { public final SmsMirroringFeatureProvider smsMirroringFeatureProvider; public final SlicesFeatureProvider slicesFeatureProvider; public SearchFeatureProvider searchFeatureProvider; + public final AccountFeatureProvider mAccountFeatureProvider; /** * Call this in {@code @Before} method of the test class to use fake factory. @@ -104,6 +106,7 @@ public class FakeFeatureFactory extends FeatureFactory { dataPlanFeatureProvider = mock(DataPlanFeatureProvider.class); smsMirroringFeatureProvider = mock(SmsMirroringFeatureProvider.class); slicesFeatureProvider = mock(SlicesFeatureProvider.class); + mAccountFeatureProvider = mock(AccountFeatureProvider.class); } @Override @@ -190,4 +193,9 @@ public class FakeFeatureFactory extends FeatureFactory { public SlicesFeatureProvider getSlicesFeatureProvider() { return slicesFeatureProvider; } + + @Override + public AccountFeatureProvider getAccountFeatureProvider() { + return mAccountFeatureProvider; + } } diff --git a/tests/robotests/src/com/android/settings/widget/AppCheckBoxPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/AppCheckBoxPreferenceTest.java new file mode 100644 index 00000000000..d540a62ec7e --- /dev/null +++ b/tests/robotests/src/com/android/settings/widget/AppCheckBoxPreferenceTest.java @@ -0,0 +1,53 @@ +/* + * 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.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class AppCheckBoxPreferenceTest { + + private Context mContext; + private AppCheckBoxPreference mPreference; + private AppCheckBoxPreference mAttrPreference; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mPreference = new AppCheckBoxPreference(mContext); + mAttrPreference = new AppCheckBoxPreference(mContext, null /* attrs */); + } + + @Test + public void testGetLayoutResource() { + assertThat(mPreference.getLayoutResource()).isEqualTo(R.layout.preference_app); + assertThat(mAttrPreference.getLayoutResource()).isEqualTo(R.layout.preference_app); + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceControllerTest.java index 6832ca8e6d8..2a633d9a6c1 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceControllerTest.java @@ -80,7 +80,7 @@ public class WifiTetherApBandPreferenceControllerTest { mController.displayPreference(mScreen); - assertThat(mListPreference.getEntries().length).isEqualTo(2); + assertThat(mListPreference.getEntries().length).isEqualTo(3); } @Test @@ -113,13 +113,18 @@ public class WifiTetherApBandPreferenceControllerTest { when(mWifiManager.is5GHzBandSupported()).thenReturn(true); mController.displayPreference(mScreen); + + // -1 is WifiConfiguration.AP_BAND_ANY, for 'Auto' option. + mController.onPreferenceChange(mListPreference, "-1"); + assertThat(mController.getBandIndex()).isEqualTo(-1); + mController.onPreferenceChange(mListPreference, "1"); assertThat(mController.getBandIndex()).isEqualTo(1); mController.onPreferenceChange(mListPreference, "0"); assertThat(mController.getBandIndex()).isEqualTo(0); - verify(mListener, times(2)).onTetherConfigUpdated(); + verify(mListener, times(3)).onTetherConfigUpdated(); } @Test diff --git a/tests/unit/src/com/android/settings/notification/ChannelGroupNotificationSettingsTest.java b/tests/unit/src/com/android/settings/notification/ChannelGroupNotificationSettingsTest.java deleted file mode 100644 index ce2c408fa50..00000000000 --- a/tests/unit/src/com/android/settings/notification/ChannelGroupNotificationSettingsTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.notification; - -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; - -import static org.hamcrest.Matchers.allOf; -import static org.junit.Assert.fail; - -import android.app.INotificationManager; -import android.app.Instrumentation; -import android.app.NotificationChannel; -import android.app.NotificationChannelGroup; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.os.Process; -import android.os.ServiceManager; -import android.provider.Settings; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ChannelGroupNotificationSettingsTest { - - private Context mTargetContext; - private Instrumentation mInstrumentation; - private NotificationManager mNm; - - @Before - public void setUp() { - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mTargetContext = mInstrumentation.getTargetContext(); - mNm = (NotificationManager) mTargetContext.getSystemService(Context.NOTIFICATION_SERVICE); - } - - @Test - public void launchNotificationSetting_displaysChannels() { - NotificationChannelGroup group = - new NotificationChannelGroup(this.getClass().getName(), this.getClass().getName()); - group.setDescription("description"); - NotificationChannel channel = new NotificationChannel(this.getClass().getName(), - "channel" + this.getClass().getName(), IMPORTANCE_MIN); - channel.setGroup(this.getClass().getName()); - NotificationChannel channel2 = new NotificationChannel("2"+this.getClass().getName(), - "2channel" + this.getClass().getName(), IMPORTANCE_MIN); - channel2.setGroup(this.getClass().getName()); - - mNm.createNotificationChannelGroup(group); - mNm.createNotificationChannel(channel); - mNm.createNotificationChannel(channel2); - - final Intent intent = new Intent(Settings.ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS) - .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()) - .putExtra(Settings.EXTRA_CHANNEL_GROUP_ID, group.getId()); - - mInstrumentation.startActivitySync(intent); - - onView(allOf(withText(group.getName().toString()))).check(matches(isDisplayed())); - onView(allOf(withText(channel.getName().toString()))).check( - matches(isDisplayed())); - onView(allOf(withText(group.getDescription().toString()))).check( - matches(isDisplayed())); - onView(allOf(withText(channel2.getName().toString()))).check( - matches(isDisplayed())); - try { - onView(allOf(withText("Android is blocking this group of notifications from" - + " appearing on this device"))).check(matches(isDisplayed())); - fail("Blocking footer erroneously appearing"); - } catch (Exception e) { - // expected - } - } - - @Test - public void launchNotificationSettings_blockedGroup() throws Exception { - NotificationChannelGroup blocked = - new NotificationChannelGroup("blocked", "blocked"); - NotificationChannel channel = - new NotificationChannel("channel", "channel", IMPORTANCE_HIGH); - channel.setGroup(blocked.getId()); - mNm.createNotificationChannelGroup(blocked); - mNm.createNotificationChannel(channel); - - INotificationManager sINM = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - blocked.setBlocked(true); - sINM.updateNotificationChannelGroupForPackage( - mTargetContext.getPackageName(), Process.myUid(), blocked); - - final Intent intent = new Intent(Settings.ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS) - .putExtra(Settings.EXTRA_APP_PACKAGE, mTargetContext.getPackageName()) - .putExtra(Settings.EXTRA_CHANNEL_GROUP_ID, blocked.getId()); - mInstrumentation.startActivitySync(intent); - - onView(allOf(withText("Off"), isDisplayed())).check(matches(isDisplayed())); - onView(allOf(withText("Android is blocking this group of notifications from" - + " appearing on this device"))).check(matches(isDisplayed())); - - try { - onView(allOf(withText(channel.getName().toString()))).check(matches(isDisplayed())); - fail("settings appearing for blocked group"); - } catch (Exception e) { - // expected - } - } -}