From ca2b1f856417700daf9c10ff1267cb056429d951 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Fri, 19 Jan 2018 20:54:20 +0100 Subject: [PATCH 01/20] DisplayCutout: Add support for multiple cutout emulation options Instead of a single emulation option, users can select from a list of different styles of cutouts. Bug: 65689439 Test: atest EmulateDisplayCutoutPreferenceControllerTest Change-Id: I75598254849c11d9973f2b9cfdbec117bc3957da --- res/values/strings.xml | 5 +- res/xml/development_settings.xml | 2 +- ...lateDisplayCutoutPreferenceController.java | 113 ++++++++++++++---- .../android/content/om/IOverlayManager.java | 5 + .../src/android/content/om/OverlayInfo.java | 9 ++ ...DisplayCutoutPreferenceControllerTest.java | 94 +++++++++------ 6 files changed, 165 insertions(+), 63 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 4182fd95f3b..419394753fc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8681,7 +8681,10 @@ Ranking object doesn\'t contain this key. - Emulate a display with a cutout + Simulate a display with a cutout + + + None Special app access diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 9d85ec9c9a1..9377fa0c5e8 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -346,7 +346,7 @@ android:key="density" android:title="@string/developer_smallest_width" /> - 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/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/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 From a8df3064cdf1b8760dc27e352c64bd412b5a6129 Mon Sep 17 00:00:00 2001 From: Ningyuan Wang Date: Wed, 17 Jan 2018 16:01:49 -0800 Subject: [PATCH 02/20] Wifi Settings: Add auto option for softap For device with 5g support, settings will display auto, 2g, and 5g options. For decice without 5g support, auto and 2g option are shown. Bug: 68763822 Test: compile Test: m RunSettingsRoboTests -j40 Test: manually test by checking UI and start hotspot with no issue Test: manually test that wifi settings logs expected values Change-Id: Iee9f8cff6aa48df80f5c228596cd9a14f884c274 --- res/values/arrays.xml | 4 +++- res/values/strings.xml | 2 ++ .../tether/WifiTetherApBandPreferenceController.java | 9 ++++++--- .../tether/WifiTetherApBandPreferenceControllerTest.java | 9 +++++++-- 4 files changed, 18 insertions(+), 6 deletions(-) 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/strings.xml b/res/values/strings.xml index 4182fd95f3b..4ca5f6e4a5e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1941,6 +1941,8 @@ Show password Select AP Band + + Auto 2.4 GHz Band 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/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 From 8c6d8daaf00f80344336ea8181d7e84a38486252 Mon Sep 17 00:00:00 2001 From: Jong Wook Kim Date: Fri, 5 Jan 2018 18:42:36 -0800 Subject: [PATCH 03/20] Wifi MAC Randomization: Developer Options Add a toggle in developers options to turn on/off connected MAC randomization for devices that support the feature. This toggle will be only available on devices that support dynamic MAC address change and indicated as so in config_wifi_support_connected_mac_randomization. The toggle changes Settings Global value WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, which the Wifi Framework will listen for to start/stop randomizing MAC addresses. Bug: 67908229 Bug: 71548421 Test: RunSettingsRoboTests, manual testing checking that the toggle comes up and changes global settings value. Change-Id: Ia4749cd0a1e7466333bd8a93088d6340f6567e25 --- res/values/config.xml | 4 + res/xml/development_settings.xml | 5 + .../DevelopmentSettingsDashboardFragment.java | 1 + ...dMacRandomizationPreferenceController.java | 93 ++++++++++++ tests/robotests/res/values-mcc999/config.xml | 1 + tests/robotests/res/values/config.xml | 1 + ...RandomizationPreferenceControllerTest.java | 133 ++++++++++++++++++ 7 files changed, 238 insertions(+) create mode 100644 src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/development/WifiConnectedMacRandomizationPreferenceControllerTest.java 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/xml/development_settings.xml b/res/xml/development_settings.xml index 9d85ec9c9a1..c3b336c2863 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -202,6 +202,11 @@ android:title="@string/wifi_verbose_logging" android:summary="@string/wifi_verbose_logging_summary"/> + + 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/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(); + } +} From 1e620957b8d16a74fb93af996e10f20bf57f8098 Mon Sep 17 00:00:00 2001 From: Daniel Nishi Date: Tue, 19 Dec 2017 10:15:14 -0800 Subject: [PATCH 04/20] Add a new About Phone page. This adds the "Me Card" page. The current functionality is to show information based upon the first account added to the system. The page shows the user's avatar, name, primary account, and phone number. Bug: 63819909 Test: Robotest Change-Id: I64bfae922e828994b2b87009d0647e67dab0da42 --- AndroidManifest.xml | 28 ++++ res/values/strings.xml | 11 ++ res/xml/me_card.xml | 50 +++++++ src/com/android/settings/MeCardFragment.java | 137 ++++++++++++++++++ src/com/android/settings/Settings.java | 1 + .../android/settings/SettingsActivity.java | 13 ++ .../accounts/AccountFeatureProvider.java | 34 +++++ .../accounts/AccountFeatureProviderImpl.java | 16 ++ .../android/settings/core/FeatureFlags.java | 1 + .../core/gateway/SettingsGateway.java | 1 + .../BrandedAccountPreferenceController.java | 78 ++++++++++ .../settings/overlay/FeatureFactory.java | 3 + .../settings/overlay/FeatureFactoryImpl.java | 11 ++ .../search/SearchIndexableResourcesImpl.java | 2 + ...randedAccountPreferenceControllerTest.java | 67 +++++++++ .../testutils/FakeFeatureFactory.java | 8 + 16 files changed, 461 insertions(+) create mode 100644 res/xml/me_card.xml create mode 100644 src/com/android/settings/MeCardFragment.java create mode 100644 src/com/android/settings/accounts/AccountFeatureProvider.java create mode 100644 src/com/android/settings/accounts/AccountFeatureProviderImpl.java create mode 100644 src/com/android/settings/deviceinfo/BrandedAccountPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/deviceinfo/BrandedAccountPreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5a4db20d8aa..a36ec8ba29c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1004,6 +1004,34 @@ android:value="true" /> + + + + + + + + + + + + + + + + + + + + + + My Phone + + My Tablet + + My Device + + Account + + Device name + diff --git a/res/xml/me_card.xml b/res/xml/me_card.xml new file mode 100644 index 00000000000..2d8c21f6073 --- /dev/null +++ b/res/xml/me_card.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/MeCardFragment.java b/src/com/android/settings/MeCardFragment.java new file mode 100644 index 00000000000..9790fd5e3fb --- /dev/null +++ b/src/com/android/settings/MeCardFragment.java @@ -0,0 +1,137 @@ +/* + * 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; + +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.view.View; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.deviceinfo.BrandedAccountPreferenceController; +import com.android.settings.deviceinfo.PhoneNumberPreferenceController; +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 MeCardFragment extends DashboardFragment implements Indexable { + private static final String LOG_TAG = "MeCardFragment"; + + private static final String KEY_ME_CARD_HEADER = "me_card_header"; + + @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.me_card; + } + + @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)); + // 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_ME_CARD_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.me_card; + return Arrays.asList(sir); + } + + @Override + public List getPreferenceControllers( + Context context) { + return buildPreferenceControllers(context, null /*activity */, + null /* fragment */, null /* lifecycle */); + } + }; +} diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index d13a62dcbb3..4503b641988 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -55,6 +55,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 */ } diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 5cb7c06c21a..cc90619431f 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -873,6 +873,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/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/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/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index 08057664c1c..80d435ff77f 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 f817d4bb218..55f408d7887 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..4067e6b69af 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.MeCardFragment; 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(MeCardFragment.class); } @Override 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/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index 3325332703b..ad72e6be2af 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; + } } From 85e2f61b2d349125e4bd5d7677cfa09ba09983ca Mon Sep 17 00:00:00 2001 From: Maggie Date: Thu, 4 Jan 2018 15:35:49 -0800 Subject: [PATCH 05/20] Remove location_modes_previous references 1. Remove reference to LOCATION_MODE_PREVIOUS. 2. Add setLocationEnabled method in LocationEnabler. When turning location ON/OFF from Settings app or Quick Settings, use LocationEnabler.setLocationEnabled() instead of setLocationMode(). 3. Change unit tests accordingly. Bug: 70990911 Test: Robolectric Test: Manual Change-Id: Ic02ef3cd02f9aa7d2ef18697b19b507575aaf5c2 --- .../settings/location/LocationEnabler.java | 21 ++++++++++ .../settings/location/LocationSettings.java | 9 +---- .../location/LocationSwitchBarController.java | 5 +-- .../widget/SettingsAppWidgetProvider.java | 30 +++++--------- .../location/LocationEnablerTest.java | 39 +++++++++++++++++-- .../LocationSwitchBarControllerTest.java | 9 ++--- 6 files changed, 73 insertions(+), 40 deletions(-) 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/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/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 From 02af3659e00fa4583a88b6756e8dc26fcbfb8765 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 22 Jan 2018 16:20:47 -0500 Subject: [PATCH 06/20] Display recent apps in notification settings The last 5 non-system apps that have sent notifications will be displayed at the top level of notification settings for easy access. Test: make -j20 RunSettingsRoboTests Change-Id: Ifaae36f977beb0438a740f61ff0ac9c97f3acc80 Fixes: 63927402 --- res/values/strings.xml | 3 + res/xml/configure_notification_settings.xml | 24 +- .../ConfigureNotificationSettings.java | 17 +- .../notification/NotificationBackend.java | 14 +- ...centNotifyingAppsPreferenceController.java | 293 +++++++++++++++++ .../service/notification/NotifyingApp.java | 112 +++++++ ...NotifyingAppsPreferenceControllerTest.java | 301 ++++++++++++++++++ 7 files changed, 758 insertions(+), 6 deletions(-) create mode 100644 src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java create mode 100644 tests/robotests/src/android/service/notification/NotifyingApp.java create mode 100644 tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 164f6d5de29..bc95ae9e880 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6965,6 +6965,9 @@ Notifications + + Recently sent + Advanced 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"> + + + + + + + + + + + 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/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/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/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/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()); + } +} From b63de874fa1a01c35901b795389c1624986d1c47 Mon Sep 17 00:00:00 2001 From: "xiyuan.wang" Date: Thu, 28 Dec 2017 11:24:40 +0800 Subject: [PATCH 07/20] The text on toast should be changed when remains 1 time for SIM lock input The behavior of getQuantityString() is different according to language, it gets both "other" and "one" string if English is system language. But it always gets "other" string if non-English, then the "other" string of wrong_pin_code is shown even if remains 1 time of SIM lock input. In order to change the string in any language, we add new string for remains 1 time. Bug: 71783850 Change-Id: I5de35eb71905b77028bf25226381e1ba79a37e92 --- res/values-ar/strings.xml | 1 + res/values-az/strings.xml | 1 + res/values-b+sr+Latn/strings.xml | 1 + res/values-be/strings.xml | 1 + res/values-bg/strings.xml | 1 + res/values-bn/strings.xml | 1 + res/values-bs/strings.xml | 1 + res/values-ca/strings.xml | 1 + res/values-cs/strings.xml | 1 + res/values-da/strings.xml | 1 + res/values-de/strings.xml | 1 + res/values-el/strings.xml | 1 + res/values-en-rGB/strings.xml | 1 + res/values-en-rIN/strings.xml | 1 + res/values-es-rUS/strings.xml | 1 + res/values-es/strings.xml | 1 + res/values-et/strings.xml | 1 + res/values-eu/strings.xml | 1 + res/values-fa/strings.xml | 1 + res/values-fi/strings.xml | 1 + res/values-fr-rCA/strings.xml | 1 + res/values-fr/strings.xml | 1 + res/values-gl/strings.xml | 1 + res/values-gu/strings.xml | 1 + res/values-hi/strings.xml | 1 + res/values-hr/strings.xml | 1 + res/values-hu/strings.xml | 1 + res/values-hy/strings.xml | 1 + res/values-in/strings.xml | 1 + res/values-is/strings.xml | 1 + res/values-it/strings.xml | 1 + res/values-iw/strings.xml | 1 + res/values-ja/strings.xml | 1 + res/values-ka/strings.xml | 1 + res/values-kk/strings.xml | 1 + res/values-km/strings.xml | 1 + res/values-kn/strings.xml | 1 + res/values-ko/strings.xml | 1 + res/values-ky/strings.xml | 1 + res/values-lo/strings.xml | 1 + res/values-lt/strings.xml | 1 + res/values-lv/strings.xml | 1 + res/values-mk/strings.xml | 1 + res/values-ml/strings.xml | 1 + res/values-mn/strings.xml | 1 + res/values-mr/strings.xml | 1 + res/values-ms/strings.xml | 1 + res/values-my/strings.xml | 1 + res/values-nb/strings.xml | 1 + res/values-ne/strings.xml | 1 + res/values-nl/strings.xml | 1 + res/values-pa/strings.xml | 1 + res/values-pl/strings.xml | 1 + res/values-pt-rBR/strings.xml | 1 + res/values-pt-rPT/strings.xml | 1 + res/values-pt/strings.xml | 1 + res/values-ro/strings.xml | 1 + res/values-ru/strings.xml | 1 + res/values-si/strings.xml | 1 + res/values-sk/strings.xml | 1 + res/values-sl/strings.xml | 1 + res/values-sq/strings.xml | 1 + res/values-sr/strings.xml | 1 + res/values-sv/strings.xml | 1 + res/values-sw/strings.xml | 1 + res/values-ta/strings.xml | 1 + res/values-te/strings.xml | 1 + res/values-th/strings.xml | 1 + res/values-tl/strings.xml | 1 + res/values-tr/strings.xml | 1 + res/values-uk/strings.xml | 1 + res/values-ur/strings.xml | 1 + res/values-uz/strings.xml | 1 + res/values-vi/strings.xml | 1 + res/values-zh-rCN/strings.xml | 1 + res/values-zh-rHK/strings.xml | 1 + res/values-zh-rTW/strings.xml | 1 + res/values-zu/strings.xml | 1 + res/values/strings.xml | 3 +++ src/com/android/settings/IccLockSettings.java | 4 +++- 80 files changed, 84 insertions(+), 1 deletion(-) diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 87f3cd7c5b0..83776361fd2 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -1169,6 +1169,7 @@ ‏رمز رقم التعريف الشخصي لبطاقة SIM غير صحيح، ويتبقى لديك %d من المحاولات. ‏رمزPIN لبطاقة SIM غير صحيح، ولديك محاولة واحدة (%d) يجب أن تتصل بعدها بمشغّل شبكة الجوّال لإلغاء قفل الجهاز. + ‏رمزPIN لبطاقة SIM غير صحيح، ولديك محاولة واحدة (%d) يجب أن تتصل بعدها بمشغّل شبكة الجوّال لإلغاء قفل الجهاز. "‏تعذّر إتمام عملية \"رقم التعريف الشخصي\" لبطاقة SIM!" "حالة الجهاز اللوحي" "حالة الهاتف" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 4b1e6139161..33bbcc36a6d 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -1117,6 +1117,7 @@ Yanlış SIM PIN kodu, %d cəhdiniz qalır. Yanlış SIM PIN kodu, cihazınızı kiliddən çıxarmaq üçün operatorunuzla əlaqə saxlamadan öncə %d cəhdiniz qalır. + Yanlış SIM PIN kodu, cihazınızı kiliddən çıxarmaq üçün operatorunuzla əlaqə saxlamadan öncə %d cəhdiniz qalır. "SIM PIN əməliyyatı alınmadı!" "Planşet statusu" "Telefon statusu" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index 43c5f3161ec..138ab5cf8a9 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -1121,6 +1121,7 @@ Netačan SIM PIN kôd. Imate još %d pokušaja. Netačan SIM PIN kôd. Imate još %d pokušaja. + Netačan SIM PIN kôd. Imate još %d pokušaj. "Radnja sa SIM PIN kodom nije uspela!" "Status tableta" "Status telefona" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index f44e779c217..2afb7bdede4 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -1129,6 +1129,7 @@ Няправільны PIN-код SIM-карты, у вас засталося %d спроб. Няправільны PIN-код SIM-карты, у вас засталося %d спробы. + Няправільны PIN-код SIM-карты, у вас засталася %d спроба. "Разблакір. SIM PIN-кодам не атрымалася!" "Стан планшэта" "Стан тэлефону" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 448171d5a1b..3b1c851f7ed 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -1117,6 +1117,7 @@ Неправилен ПИН код за SIM картата – остават ви %d опита. Неправилен ПИН код за SIM картата – остава ви %d опит, преди да трябва да се свържете с оператора си, за да отключите устройството. + Неправилен ПИН код за SIM картата – остава ви %d опит, преди да трябва да се свържете с оператора си, за да отключите устройството. "Операцията с ПИН кода за SIM картата не бе успешна!" "Състояние на таблета" "Състояние на телефона" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index 6c61c07aec1..738cdf91b3e 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -1117,6 +1117,7 @@ সিমের পিন কোডটি ভুল, আপনি আর %d বার চেষ্টা করতে পারেন। সিমের পিন কোডটি ভুল, আপনি আর %d বার চেষ্টা করতে পারেন। + সিমের পিন কোডটি ভুল, আপনি আর %d বার চেষ্টা করতে পারেন। "সিম পিন ক্রিয়াকলাপটি ব্যর্থ হয়েছে!" "ট্যাবলেট স্থিতি" "ফোন স্থিতি" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index e014da14eb7..25bf917660c 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -1124,6 +1124,7 @@ PIN kôd za SIM je netačan. Preostala su vam još %d pokušaja. PIN kôd za SIM je netačan. Preostalo vam je još %d pokušaja. + PIN za SIM je netačan. Preostao vam je još %d pokušaj. "Operacija PIN-a za SIM nije uspjela!" "Status tableta" "Status telefona" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 7d2728d9844..075c1702951 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -1117,6 +1117,7 @@ El codi PIN de la SIM no és correcte. Et queden %d intents. El codi PIN de la SIM no és correcte. Et queda %d intent; si no l\'encertes, contacta amb l\'operador de telefonia mòbil per desbloquejar el dispositiu. + El codi PIN de la SIM no és correcte. Et queda %d intent; si no l\'encertes, contacta amb l\'operador de telefonia mòbil per desbloquejar el dispositiu. "Hi ha hagut un problema en l\'operació del PIN de la SIM." "Estat de la tauleta" "Estat del telèfon" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index ad87b45fc67..09a74a70625 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -1143,6 +1143,7 @@ Zadali jste nesprávný kód PIN SIM karty. Máte ještě %d pokusů. Zadali jste nesprávný PIN SIM karty. Zbývá %d pokus, poté bude muset zařízení odemknout operátor. + Zadali jste nesprávný PIN SIM karty. Zbývá %d pokus, poté bude muset zařízení odemknout operátor. "Operace pomocí kódu PIN SIM karty se nezdařila!" "Stav tabletu" "Stav telefonu" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index d65a87bf81a..98d85b6d30f 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -1117,6 +1117,7 @@ Forkert pinkode til SIM-kort. Du har %d forsøg tilbage. Forkert pinkode til SIM-kort. Du har %d forsøg tilbage. + Forkert pinkode til SIM-kort. Du har %d forsøg tilbage. "Pinkoden til SIM-kortet blev afvist." "Status for tabletcomputeren" "Telefonstatus" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index a0c14d69a3d..64a75f02bcc 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1117,6 +1117,7 @@ Falscher PIN-Code der SIM-Karte. Du hast noch %d Versuche. Falscher PIN-Code der SIM-Karte. Noch %d Versuch, bevor Gerät vom Mobilfunkanbieter entsperrt werden muss + Falscher PIN-Code der SIM-Karte. Noch %d Versuch, bevor Gerät vom Mobilfunkanbieter entsperrt werden muss "Fehler beim Entsperren mit der PIN der SIM-Karte" "Tablet-Status" "Telefonstatus" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index b4ce2a088f4..387de539055 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -1117,6 +1117,7 @@ Λανθασμένος κωδικός PIN κάρτας SIM. Απομένουν άλλες %d προσπάθειες. Λανθασμένος κωδικός PIN κάρτας SIM. Απομένει άλλη %d προσπάθεια. Στη συνέχεια, θα πρέπει να επικοινωνήσετε με τον πάροχο κινητής τηλεφωνίας σας για να ξεκλειδώσετε τη συσκευή σας. + Λανθασμένος κωδικός PIN κάρτας SIM. Απομένει άλλη %d προσπάθεια. Στη συνέχεια, θα πρέπει να επικοινωνήσετε με τον πάροχο κινητής τηλεφωνίας σας για να ξεκλειδώσετε τη συσκευή σας. "Αποτυχία λειτουργίας κωδικού PIN κάρτας SIM!" "Κατάσταση tablet" "Κατάσταση τηλεφώνου" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index a708b37979a..1466448d08c 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -1117,6 +1117,7 @@ Incorrect SIM PIN code, you have %d remaining attempts. Incorrect SIM PIN code, you have %d remaining attempt before you must contact your operator to unlock your device. + Incorrect SIM PIN code, you have %d remaining attempt before you must contact your operator to unlock your device. "SIM PIN operation failed!" "Tablet status" "Phone status" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index a708b37979a..1466448d08c 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -1117,6 +1117,7 @@ Incorrect SIM PIN code, you have %d remaining attempts. Incorrect SIM PIN code, you have %d remaining attempt before you must contact your operator to unlock your device. + Incorrect SIM PIN code, you have %d remaining attempt before you must contact your operator to unlock your device. "SIM PIN operation failed!" "Tablet status" "Phone status" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 6daf781f721..380a48043ba 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -1117,6 +1117,7 @@ El código PIN de la tarjeta SIM es incorrecto. Tienes %d intentos más. El código PIN de la tarjeta SIM es incorrecto. Tienes %d intento más antes de que debas comunicarte con el proveedor para desbloquear el dispositivo. + El código PIN de la tarjeta SIM es incorrecto. Tienes %d intento más antes de que debas comunicarte con el proveedor para desbloquear el dispositivo. "Error al desbloquear la tarjeta SIM con el PIN" "Estado del tablet" "Estado del dispositivo" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index c688ae5ca8b..19b7b983b52 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -1117,6 +1117,7 @@ Código PIN de la tarjeta SIM incorrecto. Te quedan %d intentos. Código PIN de la tarjeta SIM incorrecto. Te queda %d intento para tener que ponerte en contacto con tu operador para desbloquear el dispositivo. + Código PIN de la tarjeta SIM incorrecto. Te queda %d intento para tener que ponerte en contacto con tu operador para desbloquear el dispositivo. "Error al intentar desbloquear la tarjeta SIM con el código PIN" "Estado del tablet" "Estado del teléfono" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index fa2818e5cce..cc024f8e291 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -1117,6 +1117,7 @@ Vale SIM-kaardi PIN-kood, teil on jäänud veel %d katset. Vale SIM-kaardi PIN-kood, jäänud on %d katse enne, kui peate seadme avam. operaatoriga ühendust võtma. + Vale SIM-kaardi PIN-kood, jäänud on %d katse enne, kui peate seadme avam. operaatoriga ühendust võtma. "SIM-i PIN-koodi toiming ebaõnnestus." "Tahvelarvuti olek" "Telefoni olek" diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 4dfc0eb068f..281c642a302 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -1117,6 +1117,7 @@ SIM txartelaren PIN kodea okerra da. %d saiakera geratzen zaizkizu gailua desblokeatzeko. SIM txartelaren PIN kodea okerra da. %d saiakera geratzen zaizu gailua desblokeatzeko. + SIM txartelaren PIN kodea okerra da. %d saiakera geratzen zaizu gailua desblokeatzeko. "SIMaren PIN kodearen eragiketak huts egin du!" "Tabletaren egoera" "Telefonoaren egoera" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 9a61511cf11..3086a5d966b 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -1117,6 +1117,7 @@ کد پین سیم‌کارت اشتباه است، %d بار دیگر می‌توانید تلاش کنید. کد پین سیم‌کارت اشتباه است، %d بار دیگر می‌توانید تلاش کنید. + کد پین سیم‌کارت اشتباه است، %d بار دیگر می‌توانید تلاش کنید. "عملیات پین سیم کارت ناموفق بود!" "وضعیت رایانهٔ لوحی" "وضعیت تلفن" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 61d1f058c43..3baadaf61d9 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -1117,6 +1117,7 @@ Virheellinen SIM-kortin PIN-koodi. Sinulla on %d yritystä jäljellä. Virheellinen SIM-kortin PIN-koodi. Sinulla on %d yritys jäljellä, ennen kuin sinun on otettava yhteyttä operaattoriin laitteen lukituksen avaamiseksi. + Virheellinen SIM-kortin PIN-koodi. Sinulla on %d yritys jäljellä, ennen kuin sinun on otettava yhteyttä operaattoriin laitteen lukituksen avaamiseksi. "SIM-kortin PIN-toiminto epäonnistui!" "Tablet-laitteen tila" "Puhelimen tila" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index a9da44a0034..ad735a68ffb 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -1117,6 +1117,7 @@ Le NIP de la carte SIM incorrect. Il vous reste %d tentative. Le NIP de la carte SIM incorrect. Il vous reste %d tentatives. + Le NIP de la carte SIM incorrect. Il vous reste %d tentative. "Le déverrouillage par NIP de carte SIM a échoué." "État de la tablette" "État du téléphone" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 1d30bc640c5..40cd52d1049 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -1117,6 +1117,7 @@ Code PIN de la carte SIM erroné. Il vous reste %d tentative. Code PIN de la carte SIM erroné. Il vous reste %d tentatives. + Code PIN de la carte SIM erroné. Il vous reste %d tentative. "Échec du déverrouillage à l\'aide du code PIN de la carte SIM." "État de la tablette" "État du téléphone" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 76ca54a1296..58f6402f002 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -1117,6 +1117,7 @@ O código PIN da SIM é incorrecto. Quédanche %d intentos. O código PIN da SIM é incorrecto. Quédache %d intento antes de que teñas que contactar co operador para desbloquear o dispositivo. + O código PIN da SIM é incorrecto. Quédache %d intento antes de que teñas que contactar co operador para desbloquear o dispositivo. "Fallo no funcionamento do PIN da SIM" "Estado da tableta" "Estado do teléfono" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index 83322f49df7..bd51b4a697f 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -1117,6 +1117,7 @@ ખોટો સિમ પિન કોડ, તમારી પાસે %d પ્રયાસ બાકી છે. ખોટો સિમ પિન કોડ, તમારી પાસે %d પ્રયાસ બાકી છે. + ખોટો સિમ પિન કોડ, તમારી પાસે %d પ્રયાસ બાકી છે. "સિમ પિન ઑપરેશન નિષ્ફળ થયું!" "ટેબ્લેટ સ્થિતિ" "ફોન સ્થિતિ" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index b0555c853e7..f47ec744454 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -1117,6 +1117,7 @@ गलत सिम पिन कोड, आप %d बार और कोशिश कर सकते हैं. गलत सिम पिन कोड, आप %d बार और कोशिश कर सकते हैं. + गलत सिम पिन कोड, आप %d बार और कोशिश कर सकते हैं. "सिम PIN की कार्यवाही विफल रही!" "टैबलेट स्‍थिति" "फ़ोन स्‍थिति" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index ecf7257fda2..e3583f55ad3 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -1130,6 +1130,7 @@ Netočan PIN kôd SIM kartice; imate još %d pokušaja. Netočan PIN kôd SIM kartice; imate još %d pokušaja. + Netočan PIN kôd SIM kartice; imate još %d pokušaj. "Operacija PIN-a SIM kartice nije uspjela!" "Status tabletnog uređaja" "Status telefona" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index fbcca9a3488..859af0e8a5d 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -1117,6 +1117,7 @@ A SIM-kártya PIN-kódja helytelen. %d próbálkozás maradt. A SIM-kártya PIN-kódja helytelen. %d próbálkozás maradt. Utána a szolgáltatótól kell feloldást kérnie. + A SIM-kártya PIN-kódja helytelen. %d próbálkozás maradt. Utána a szolgáltatótól kell feloldást kérnie. "A SIM-kártya PIN-művelete sikertelen!" "Táblagép állapota" "Telefon állapota" diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index 5feaa6d51aa..82b66f17c14 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -1117,6 +1117,7 @@ SIM PIN կոդը սխալ է: Մնաց %d փորձ: SIM PIN կոդը սխալ է: Մնաց %d փորձ: + SIM PIN կոդը սխալ է: Մնաց %d փորձ: "SIM PIN կոդի գործողությունը ձախողվեց:" "Պլանշետի կարգավիճակը" "Հեռախոսի կարգավիճակը" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 1b6b728a88c..827dfa3204a 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -1117,6 +1117,7 @@ Kode PIN SIM salah, sisa %d percobaan. Kode PIN SIM salah, sisa %d percobaan sebelum Anda harus menghubungi operator untuk membuka kunci perangkat. + Kode PIN SIM salah, sisa %d percobaan sebelum Anda harus menghubungi operator untuk membuka kunci perangkat. "Operasi PIN SIM gagal!" "Status tablet" "Status ponsel" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index 05122942aaa..8c7b9c42a19 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -1117,6 +1117,7 @@ Rangt PIN-númer SIM-korts. Þú átt %d tilraun eftir. Rangt PIN-númer SIM-korts. Þú átt %d tilraunir eftir. + Rangt PIN-númer SIM-korts. Þú átt %d tilraun eftir. "PIN-aðgerð SIM-korts mistókst!" "Staða spjaldtölvu" "Staða síma" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index ab3dcc3ec50..b22d3cfb7f0 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -1117,6 +1117,7 @@ Codice PIN della SIM errato. Hai ancora %d tentativi a disposizione. Codice PIN della SIM errato. Hai ancora %d tentativo a disposizione, dopodiché dovrai contattare l\'operatore per sbloccare il dispositivo. + Codice PIN della SIM errato. Hai ancora %d tentativo a disposizione, dopodiché dovrai contattare l\'operatore per sbloccare il dispositivo. "Operazione con PIN della SIM non riuscita." "Stato tablet" "Stato telefono" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 7afdfdd7cb7..9d2f7e504bd 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -1143,6 +1143,7 @@ ‏קוד גישה שגוי של כרטיס SIM. נותרו לך %d ניסיונות. ‏קוד גישה שגוי של כרטיס SIM. נותר לך ניסיון %d לפני שיהיה עליך ליצור קשר עם הספק כדי לבטל את נעילת המכשיר. + ‏קוד גישה שגוי של כרטיס SIM. נותר לך ניסיון %d לפני שיהיה עליך ליצור קשר עם הספק כדי לבטל את נעילת המכשיר. "‏פעולת קוד הגישה של כרטיס ה-SIM נכשלה!" "סטטוס טאבלט" "סטטוס הטלפון" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index f5d094bbee9..4b1b7f689d6 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -1117,6 +1117,7 @@ SIM PINコードが無効です。入力できるのはあと%d回です。 SIM PINコードが無効です。入力できるのはあと%d回です。この回数を超えた場合は、携帯通信会社にお問い合わせください。 + SIM PINコードが無効です。入力できるのはあと%d回です。この回数を超えた場合は、携帯通信会社にお問い合わせください。 "SIM PIN操作に失敗しました。" "タブレットの状態" "端末の状態" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index ccd0e2374d3..c76c3fbf869 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -1117,6 +1117,7 @@ SIM-ის PIN არასწორია. დაგრჩათ %d მცდელობა. SIM-ის PIN არასწორია. დაგრჩათ %d მცდელობა, სანამ მოგიწევთ მოწყობილობის განსაბლოკად ოპერატორთან დაკავშირება. + SIM-ის PIN არასწორია. დაგრჩათ %d მცდელობა, სანამ მოგიწევთ მოწყობილობის განსაბლოკად ოპერატორთან დაკავშირება. "SIM PIN ოპერაცია ჩაიშალა!" "ტაბლეტის სტატუსი" "ტელეფონის სტატუსი" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index 5ef38801aa8..690f4d164c7 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -1117,6 +1117,7 @@ SIM PIN коды дұрыс емес, %d әрекет қалды. SIM PIN коды дұрыс емес, операторға құрылғы бекітпесін ашуы үшін хабарласуға дейін %d әрекет қалды. + SIM PIN коды дұрыс емес, операторға құрылғы бекітпесін ашуы үшін хабарласуға дейін %d әрекет қалды. "SIM PIN жұмысы орындалмады!" "Планшет күйі" "Телефон күйі" diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index 0e086842169..8e5af2b8f51 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -1117,6 +1117,7 @@ លេខកូដសម្ងាត់ស៊ីមមិនត្រឹមត្រូវ អ្នកនៅសល់ការព្យាយាម %d ដងទៀត។ លេខកូដសម្ងាត់ស៊ីមមិនត្រឹមត្រូវ អ្នកនៅសល់ការព្យាយាម %d ដងទៀត មុនពេលពេលដែលអ្នកត្រូវទាក់ទងទៅអ្នកផ្តល់សេវាកម្មរបស់អ្នកដើម្បីដោះសោឧបករណ៍របស់អ្នក។ + លេខកូដសម្ងាត់ស៊ីមមិនត្រឹមត្រូវ អ្នកនៅសល់ការព្យាយាម %d ដងទៀត មុនពេលពេលដែលអ្នកត្រូវទាក់ទងទៅអ្នកផ្តល់សេវាកម្មរបស់អ្នកដើម្បីដោះសោឧបករណ៍របស់អ្នក។ "បាន​បរាជ័យ​ក្នុង​ការ​ប្រតិបត្តិ​​លេខ​កូដ PIN ស៊ីម!" "ស្ថានភាព​កុំព្យូទ័រ​បន្ទះ" "ស្ថានភាព​ទូរស័ព្ទ" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 25ef7818f8a..825d2ee8a62 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -1117,6 +1117,7 @@ ಸಿಮ್‌ ಪಿನ್ ಕೋಡ್‌ ತಪ್ಪಾಗಿದೆ, ನಿಮ್ಮಲ್ಲಿ %d ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. ಸಿಮ್‌ ಪಿನ್ ಕೋಡ್‌ ತಪ್ಪಾಗಿದೆ, ನಿಮ್ಮಲ್ಲಿ %d ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. + ಸಿಮ್‌ ಪಿನ್ ಕೋಡ್‌ ತಪ್ಪಾಗಿದೆ, ನಿಮ್ಮಲ್ಲಿ %d ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. "ಸಿಮ್‌ ಪಿನ್‌ ಕಾರ್ಯಾಚರಣೆ ವಿಫಲವಾಗಿದೆ!" "ಟ್ಯಾಬ್ಲೆಟ್ ಸ್ಥಿತಿ" "ಫೋನ್ ಸ್ಥಿತಿ" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index ae3ad74281d..927ad0878e5 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -1117,6 +1117,7 @@ SIM PIN 코드가 잘못되었습니다. %d번 더 시도할 수 있습니다. SIM PIN 코드가 잘못되었습니다. %d번 더 실패하면 이동통신사에 문의하여 기기를 잠금 해제해야 합니다. + SIM PIN 코드가 잘못되었습니다. %d번 더 실패하면 이동통신사에 문의하여 기기를 잠금 해제해야 합니다. "SIM PIN 작업이 실패했습니다." "태블릿 상태" "휴대전화 상태" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 1f48df51ff7..f77e8c60a0c 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -1116,6 +1116,7 @@ SIM PIN-коду туура эмес, сизде %d аракет калды. SIM PIN-коду туура эмес, түзмөк кулпусун ачуу үчүн операторуңузга кайрылуудан мурун %d аракет калды. + SIM PIN-коду туура эмес, түзмөк кулпусун ачуу үчүн операторуңузга кайрылуудан мурун %d аракет калды. "SIM-карта PIN аракети кыйрады!" "Планшеттин абалы" "Телефондун абалы" diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index 44d2bd57723..7056ff4d76c 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -1117,6 +1117,7 @@ ລະຫັດ SIM PIN ບໍ່ຖືກຕ້ອງ, ທ່ານຍັງພະຍາຍາມໄດ້ອີກ %d ຄັ້ງ. ລະຫັດ PIN ຂອງ SIM ບໍ່ຖືກຕ້ອງ, ທ່ານສາມາດລອງໄດ້ອີກ %d ເທື່ອກ່ອນທີ່ທ່ານຈະຕ້ອງຕິດຕໍ່ຫາຜູ່ໃຫ້ບໍລິການຂອງທ່ານ ເພື່ອປົດລັອກອຸປະກອນຂອງທ່ານ. + ລະຫັດ PIN ຂອງ SIM ບໍ່ຖືກຕ້ອງ, ທ່ານສາມາດລອງໄດ້ອີກ %d ເທື່ອກ່ອນທີ່ທ່ານຈະຕ້ອງຕິດຕໍ່ຫາຜູ່ໃຫ້ບໍລິການຂອງທ່ານ ເພື່ອປົດລັອກອຸປະກອນຂອງທ່ານ. "PIN ຂອງ SIM ເຮັດວຽກລົ້ມເຫຼວ!" "ສະຖານະແທັບເລັດ" "ສະ​ຖາ​ນະ​ໂທລະ​ສັບ" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 075e437412b..f637d71a52a 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -1143,6 +1143,7 @@ Netinkamas SIM kortelės PIN kodas. Liko %d bandymo. Netinkamas SIM kortelės PIN kodas. Liko %d bandymų. + Netinkamas SIM kortelės PIN kodas. Liko %d bandymas. "Nepavyko atlikti SIM kortelės PIN kodo operacijos." "Planšetinio kompiuterio būsena" "Telefono būsena" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index dd2d012a9df..7c826ea83d3 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -1130,6 +1130,7 @@ Nepareizs SIM kartes PIN kods. Varat mēģināt vēl %d reizi. Nepareizs SIM kartes PIN kods. Varat mēģināt vēl %d reizes. + Nepareizs SIM kartes PIN kods. Varat mēģināt vēl %d reizi. "SIM kartes PIN koda ievadīšana neizdevās." "Planšetdatora statuss" "Tālruņa statuss" diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 76061cab38a..405498012d6 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -1117,6 +1117,7 @@ Погрешен PIN-код за SIM, имате уште %d обид. Погрешен PIN-код за SIM, имате уште %d обиди. + Погрешен PIN-код за SIM, имате уште %d обид. "Операцијата PIN на SIM картичка не успеа!" "Статус на таблет" "Статус на телефон" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index c81e3fe95f7..63814bcafea 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -1117,6 +1117,7 @@ SIM PIN കോഡ് തെറ്റാണ്, നിങ്ങൾക്ക് %d ശ്രമങ്ങൾ കൂടി ശേഷിക്കുന്നു. SIM PIN കോഡ് തെറ്റാണ്, നിങ്ങളുടെ ഉപകരണം അൺലോക്കുചെയ്യാൻ കാരിയറെ ബന്ധപ്പെടേണ്ടതിന് മുമ്പായി %d ശ്രമം കൂടി ശേഷിക്കുന്നു. + SIM PIN കോഡ് തെറ്റാണ്, നിങ്ങളുടെ ഉപകരണം അൺലോക്കുചെയ്യാൻ കാരിയറെ ബന്ധപ്പെടേണ്ടതിന് മുമ്പായി %d ശ്രമം കൂടി ശേഷിക്കുന്നു. "സിം പിൻ പ്രവർത്തനം പരാജയപ്പെട്ടു!" "ടാബ്‌ലെറ്റ് നില" "ഫോൺ നില" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index d3fe39effe9..66df6cd1852 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -1117,6 +1117,7 @@ СИМ-ны ПИН код буруу байна. Та %d удаа оролдлого хийх боломжтой байна. СИМ-ны ПИН код буруу байна. Танд мобайл оператортойгоо холбогдохгүйгээр төхөөрөмжийн түгжээг тайлахад %d оролдлого хийх боломж үлдсэн байна. + СИМ-ны ПИН код буруу байна. Танд мобайл оператортойгоо холбогдохгүйгээр төхөөрөмжийн түгжээг тайлахад %d оролдлого хийх боломж үлдсэн байна. "СИМ ПИН ажиллуулах амжилтгүй боллоо!" "Таблетын статус" "Утасны статус" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index f208fd9dce1..dc4e9fd2aa6 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -1117,6 +1117,7 @@ चुकीचा सिम पिन कोड, तुमच्याकडे %d प्रयत्न शिल्लक आहे. चुकीचा सिम पिन कोड, तुमच्याकडे %d प्रयत्न शिल्लक आहेत. + चुकीचा सिम पिन कोड, तुमच्याकडे %d प्रयत्न शिल्लक आहे. "सिम पिन ऑपरेशन अयशस्वी!" "टॅबलेट स्थिती" "फोन स्थिती" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index 1dc869ff892..803a86905ba 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -1117,6 +1117,7 @@ Kod PIN SIM salah, anda ada %d cubaan lagi. Kod PIN SIM tidak betul. Anda ada %d cubaan lagi sebelum anda harus menghubungi pembawa anda untuk membuka kunci peranti. + Kod PIN SIM tidak betul. Anda ada %d cubaan lagi sebelum anda harus menghubungi pembawa anda untuk membuka kunci peranti. "Operasi PIN SIM gagal!" "Status tablet" "Status telefon" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index cd01e042c8d..5e0c149a7e7 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -1117,6 +1117,7 @@ ဆင်းမ်ကဒ်၏ ပင်နံပါတ် မှားနေပါသည်၊ သင့်တွင် %d ခါ ကြိုးစားခွင့် ကျန်ပါသေးသည်။ ဆင်းမ်ကဒ်၏ ပင်နံပါတ် မှားနေပါသည်၊ သင့်ကိရိယာကို ဖွင့်ရန် မိုဘိုင်းဖုန်းဆက်သွယ်ရေးဝန်ဆောင်မှုဌာနသို့ မဆက်သွယ်မီ သင့်တွင် %d ခါ ကြိုးစားခွင့် ကျန်ပါသေးသည်။ + ဆင်းမ်ကဒ်၏ ပင်နံပါတ် မှားနေပါသည်၊ သင့်ကိရိယာကို ဖွင့်ရန် မိုဘိုင်းဖုန်းဆက်သွယ်ရေးဝန်ဆောင်မှုဌာနသို့ မဆက်သွယ်မီ သင့်တွင် %d ခါ ကြိုးစားခွင့် ကျန်ပါသေးသည်။ "ဆင်းမ်ကဒ် ပင်လုပ်ငန်းဆောင်ရွက်မှု မအောင်မြင်ပါ!" "တက်ဘလက်အခြေအနေ" "ဖုန်းအခြေအနေ" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 7d316c1c72f..a57f5b7bd0e 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -1117,6 +1117,7 @@ Feil PIN-kode for SIM-kortet. Du har %d forsøk igjen. Feil PIN-kode for SIM-kortet. Du har %d forsøk igjen før du må kontakte operatøren din for å låse opp enheten. + Feil PIN-kode for SIM-kortet. Du har %d forsøk igjen før du må kontakte operatøren din for å låse opp enheten. "PIN-koden for SIM-kortet ble avvist." "Status for nettbrettet" "Telefonstatus" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index fb0ee703a44..133524b89af 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -1117,6 +1117,7 @@ गलत SIM PIN कोड, तपाईं सँग %d पटक प्रयास बाँकी छ। SIM PIN कोड गलत छ, तपाईंले अाफ्नो यन्त्र खोल्नलाई तपाईंको वाहकसँग सम्पर्क गर्नै पर्न अघि तपाईंसँग %d पटक प्रयास बाँकी छ। + SIM PIN कोड गलत छ, तपाईंले अाफ्नो यन्त्र खोल्नलाई तपाईंको वाहकसँग सम्पर्क गर्नै पर्न अघि तपाईंसँग %d पटक प्रयास बाँकी छ। "SIM PIN कार्य बिफल भयो!" "ट्याब्लेट वस्तुस्थिति" "फोनको स्थिति" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index b4f99b04803..2e3de75e053 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -1117,6 +1117,7 @@ Onjuiste pincode voor simkaart. Je hebt nog %d pogingen over. Onjuiste pincode voor simkaart. Je hebt nog %d poging over voordat je contact met je provider moet opnemen om je apparaat te ontgrendelen. + Onjuiste pincode voor simkaart. Je hebt nog %d poging over voordat je contact met je provider moet opnemen om je apparaat te ontgrendelen. "Bewerking met pincode voor simkaart mislukt." "Tabletstatus" "Telefoonstatus" diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index de95e3b0c4c..c37883f5ade 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -1117,6 +1117,7 @@ ਗਲਤ ਸਿਮ ਪਿੰਨ ਕੋਡ, ਤੁਹਾਡੇ ਕੋਲ %d ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ। ਗਲਤ ਸਿਮ ਪਿੰਨ ਕੋਡ, ਤੁਹਾਡੇ ਕੋਲ %d ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ। + ਗਲਤ ਸਿਮ ਪਿੰਨ ਕੋਡ, ਤੁਹਾਡੇ ਕੋਲ %d ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ। "ਸਿਮ ਪਿੰਨ ਕਾਰਵਾਈ ਅਸਫਲ ਰਹੀ!" "ਟੈਬਲੈੱਟ ਸਥਿਤੀ" "ਫ਼ੋਨ ਸਥਿਤੀ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index ff0aa82cf51..55d02ba9b8f 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -1143,6 +1143,7 @@ Nieprawidłowy kod PIN karty SIM. Masz jeszcze %d próby. Nieprawidłowy kod PIN karty SIM. Masz jeszcze %d próbę, zanim będziesz musiał skontaktować się z operatorem, by odblokować swoje urządzenie. + Nieprawidłowy kod PIN karty SIM. Masz jeszcze %d próbę, zanim będziesz musiał skontaktować się z operatorem, by odblokować swoje urządzenie. "Operacja z kodem PIN karty SIM nie udała się." "Stan tabletu" "Stan telefonu" diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml index 36ab3535a27..1e05bcdd6cf 100644 --- a/res/values-pt-rBR/strings.xml +++ b/res/values-pt-rBR/strings.xml @@ -1117,6 +1117,7 @@ Código PIN do SIM incorreto. Tentativas restantes: %d. Código PIN do SIM incorreto. Tentativas restantes: %d. + Código PIN do SIM incorreto. Tentativas restantes: %d. "Falha na operação de PIN do SIM." "Status do tablet" "Status do telefone" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index ce9bd8e6049..882b9c05afe 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -1117,6 +1117,7 @@ Código PIN incorreto. Tem mais %d tentativa antes de ter de contactar operador p/ desbloquear dispos. Código PIN do cartão SIM incorreto. Tem mais %d tentativas. + Código PIN incorreto. Tem mais %d tentativa antes de ter de contactar operador p/ desbloquear dispos. "Falha ao introduzir o PIN do cartão SIM!" "Estado do tablet" "Estado do telefone" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 36ab3535a27..1e05bcdd6cf 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -1117,6 +1117,7 @@ Código PIN do SIM incorreto. Tentativas restantes: %d. Código PIN do SIM incorreto. Tentativas restantes: %d. + Código PIN do SIM incorreto. Tentativas restantes: %d. "Falha na operação de PIN do SIM." "Status do tablet" "Status do telefone" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index b90677257e4..054e891c11b 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -1130,6 +1130,7 @@ Codul PIN pentru cardul SIM este incorect. V-au mai rămas %d de încercări. Cod PIN incorect pt. card SIM. %d încercare rămasă, apoi deblocați dispozitivul contactând operatorul. + Cod PIN incorect pt. card SIM. %d încercare rămasă, apoi deblocați dispozitivul contactând operatorul. "Deblocarea cu ajutorul codului PIN pentru cardul SIM nu a reușit!" "Stare tabletă" "Stare telefon" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index e895365e4df..9734e0abeb8 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -1143,6 +1143,7 @@ Неверный PIN-код. Осталось %d попыток. После этого SIM-карта будет заблокирована и вам придется обратиться к оператору связи. Неверный PIN-код. Осталось %d попыток. После этого SIM-карта будет заблокирована и вам придется обратиться к оператору связи. + Неверный PIN-код. Осталась %d попытка. После этого SIM-карта будет заблокирована и вам придется обратиться к оператору связи. "Не удалось разблокировать SIM-карту" "Состояние планшетного ПК" "Состояние телефона" diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index f4b58d02f08..fb12382bb0b 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -1117,6 +1117,7 @@ වැරදී SIM PIN කේතයකි, ඔබගේ දුරකථනයේ අඟුල හැරීමට ඔබගේ වාහකයා සම්බන්ධ කරගැනීමට පෙර ඔබ සතුව තවත් උත්සාහයන් %d ක් ඉතිරිව ඇත. වැරදී SIM PIN කේතයකි, ඔබගේ දුරකථනයේ අඟුල හැරීමට ඔබගේ වාහකයා සම්බන්ධ කරගැනීමට පෙර ඔබ සතුව තවත් උත්සාහයන් %d ක් ඉතිරිව ඇත. + වැරදී SIM PIN කේතයකි, ඔබගේ දුරකථනයේ අඟුල හැරීමට ඔබගේ වාහකයා සම්බන්ධ කරගැනීමට පෙර ඔබ සතුව තවත් උත්සාහයන් %d ක් ඉතිරිව ඇත. "SIM PIN ක්‍රියාවලිය අපොහොසත් විය!" "ටැබ්ලට් තත්වය" "දුරකථනයේ තත්වය" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 1f13d7e6676..0dc8acb1444 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -1143,6 +1143,7 @@ Nesprávny kód PIN SIM karty. Zostáva vám %d pokusov. Nesprávny kód PIN SIM karty. Zostáva vám %d pokus, potom budete musieť kontaktovať svojho operátora, aby vám odomkol zariadenie. + Nesprávny kód PIN SIM karty. Zostáva vám %d pokus, potom budete musieť kontaktovať svojho operátora, aby vám odomkol zariadenie. "Operácia kódu PIN SIM karty zlyhala!" "Stav tabletu" "Stav telefónu" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 33e8b86d313..6ab2b78b5db 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -1143,6 +1143,7 @@ Napačna koda PIN kartice SIM. Na voljo imate še %d poskuse. Napačna koda PIN kartice SIM. Na voljo imate še %d poskusov. + Napačna koda PIN kartice SIM. Na voljo imate še %d poskus. "Postopek za odklepanje s kodo PIN kartice SIM ni uspel." "Stanje tabličnega računalnika" "Stanje telefona" diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index de00250e176..974f88cb3ad 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -1117,6 +1117,7 @@ Kodi PIN i kartës SIM është i pasaktë. Të kanë mbetur edhe %d tentativa. Kodi PIN i kartës SIM është i pasaktë. Të ka mbetur edhe %d tentativë para se të duhet të kontaktosh me operatorin tënd celular për të shkyçur pajisjen. + Kodi PIN i kartës SIM është i pasaktë. Të ka mbetur edhe %d tentativë para se të duhet të kontaktosh me operatorin tënd celular për të shkyçur pajisjen. "Operac. kodit PIN të kartës SIM dështoi!" "Statusi i tabletit" "Statusi i telefonit" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 1b619f78f4a..d5c5a1c230d 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -1130,6 +1130,7 @@ Нетачан SIM PIN кôд. Имате још %d покушаја. Нетачан SIM PIN кôд. Имате још %d покушаја. + Нетачан SIM PIN кôд. Имате још %d покушај. "Радња са SIM PIN кодом није успела!" "Статус таблета" "Статус телефона" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 8429ea4eab0..d1da2e24757 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -1117,6 +1117,7 @@ Du angav fel pinkod för SIM-kortet. %d försök återstår. Du angav fel pinkod för SIM-kortet. %d försök återstår innan du måste kontakta operatören för att låsa upp enheten. + Du angav fel pinkod för SIM-kortet. %d försök återstår innan du måste kontakta operatören för att låsa upp enheten. "Det gick inte att låsa upp med pinkoden för SIM-kortet." "Pekdatorns status" "Telefonens status" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index ab362099aeb..1336e3b984c 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -1117,6 +1117,7 @@ Nambari ya PIN ya SIM si sahihi. Una nafasi zingine %d za kujaribu. Nambari ya PIN ya SIM si sahihi. Una nafasi zingine %d za kujaribu kabla ulazimike kuwasiliana na mtoa huduma wako ili afungue kifaa chako. + Nambari ya PIN ya SIM si sahihi. Una nafasi zingine %d za kujaribu kabla ulazimike kuwasiliana na mtoa huduma wako ili afungue kifaa chako. "Utendakazi wa PIN ya SIM umeshindwa!" "Hali ya kompyuta kibao" "Hali ya simu" diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index 6cabdb76fe8..2156bfec690 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -1117,6 +1117,7 @@ சிம்மின் பின் குறியீடு தவறானது, உங்களிடம் %d முயற்சிகள் மீதமுள்ளன. சிம்மின் பின் குறியீடு தவறானது, மேலும் %d முயற்சிக்குப் பின்னர், சாதனத்தைத் திறக்க, கண்டிப்பாக உங்கள் மொபைல் நிறுவனத்தைத் தொடர்புகொள்ள வேண்டும். + சிம்மின் பின் குறியீடு தவறானது, மேலும் %d முயற்சிக்குப் பின்னர், சாதனத்தைத் திறக்க, கண்டிப்பாக உங்கள் மொபைல் நிறுவனத்தைத் தொடர்புகொள்ள வேண்டும். "சிம் பின் செயல்பாடு தோல்வி!" "டேப்லெட் நிலை" "மொபைலின் நிலை" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 39d20e074ea..0aafc39038d 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -1117,6 +1117,7 @@ SIM పిన్ కోడ్ తప్పు, మీకు మరో %d ప్రయత్నాలు మిగిలి ఉన్నాయి. SIM పిన్ కోడ్ చెల్లదు, మీరు మీ డివైజ్‌ను అన్‌లాక్ చేయడానికి తప్పనిసరిగా మీ క్యారియర్‌ను సంప్రదించడానికి ముందు మీకు %d ప్రయత్నం మిగిలి ఉంది. + SIM పిన్ కోడ్ చెల్లదు, మీరు మీ డివైజ్‌ను అన్‌లాక్ చేయడానికి తప్పనిసరిగా మీ క్యారియర్‌ను సంప్రదించడానికి ముందు మీకు %d ప్రయత్నం మిగిలి ఉంది. "సిమ్ పిన్ చర్య విఫలమైంది!" "టాబ్లెట్ స్థితి" "ఫోన్ స్థితి" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 3814d2d2e4d..8e2b10024aa 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -1117,6 +1117,7 @@ รหัส PIN ของซิมไม่ถูกต้อง คุณพยายามได้อีก %d ครั้ง รหัส PIN ของซิมไม่ถูกต้อง คุณพยายามได้อีก %d ครั้งก่อนที่จะต้องติดต่อผู้ให้บริการเพื่อปลดล็อกอุปกรณ์ + รหัส PIN ของซิมไม่ถูกต้อง คุณพยายามได้อีก %d ครั้งก่อนที่จะต้องติดต่อผู้ให้บริการเพื่อปลดล็อกอุปกรณ์ "การปลดล็อกด้วย PIN ของซิมล้มเหลว!" "สถานะแท็บเล็ต" "สถานะโทรศัพท์" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 651439bc50a..937546177a7 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -1117,6 +1117,7 @@ Maling SIM PIN code, %d subok na lang bago ka makipag-ugnayan sa carrier mo upang i-unlock ang device. Maling SIM PIN code, %d subok na lang bago ka makipag-ugnayan sa carrier mo upang i-unlock ang device. + Maling SIM PIN code, %d subok na lang bago ka makipag-ugnayan sa carrier mo upang i-unlock ang device. "Nabigo ang operasyon ng SIM PIN!" "Katayuan ng tablet" "Katayuan ng telepono" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index a255a3f4fe3..48609073ac8 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -1117,6 +1117,7 @@ Yanlış SIM PIN kodu, %d deneme hakkınız kaldı. Yanlış SIM PIN kodu. Cihazınızın kilidini açmak için operatörünüzle bağlantı kurmak zorunda kalmadan önce %d deneme hakkınız kaldı. + Yanlış SIM PIN kodu. Cihazınızın kilidini açmak için operatörünüzle bağlantı kurmak zorunda kalmadan önce %d deneme hakkınız kaldı. "SIM PIN işlemi başarısız oldu!" "Tabletin durumu" "Telefon durumu" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 4303552c1c7..58a57aaf2eb 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -1143,6 +1143,7 @@ Неправильний PIN-код SIM-карти. У вас залишилося %d спроб. Неправильний PIN-код SIM-карти. У вас залишилося %d спроби. + Неправильний PIN-код SIM-карти. У вас залишилась %d спроба. "Помилка введення PIN-коду SIM-карти." "Стан пристрою" "Стан телефона" diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index 7820abe9b55..5877dc642d0 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -1117,6 +1117,7 @@ ‏غلط SIM PIN کوڈ، آپ کے پاس %d کوششیں بچی ہیں۔ ‏غلط SIM PIN کوڈ، آپ کے پاس %d کوشش بچی ہے، اس کے بعد آپ کو اپنا آلہ غیر مقفل کرنے کیلئے اپنے کریئر سے رابطہ کرنا ہوگا۔ + SIM PIN کوڈ، آپ کے پاس %d کوشش بچی ہے، اس کے بعد آپ کو اپنا آلہ غیر مقفل کرنے کیلئے اپنے کریئر سے رابطہ کرنا ہوگا۔ "‏SIM کے PIN کا عمل ناکام ہوگیا!" "ٹیبلٹ کی حیثیت" "فون اسٹیٹس" diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index cb13e4e7160..17161f7c952 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -1117,6 +1117,7 @@ SIM kartaning PIN kodi xato. Qurilma qulfini ochish uchun sizda yana %d ta urinish qoldi. SIM kartaning PIN kodi xato. Qurilma qulfini ochish uchun sizda yana %d ta urinish qoldi. + SIM kartaning PIN kodi xato. Qurilma qulfini ochish uchun sizda yana %d ta urinish qoldi. "SIM karta PIN kodining amaliyoti bajarilmadi!" "Planshet holati" "Telefon holati" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 68d966a24fa..f1fb61dc012 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -1117,6 +1117,7 @@ Mã PIN SIM của bạn không chính xác, bạn còn %d lần thử. Mã PIN của SIM không chính xác, bạn còn %d lần thử trước khi bạn phải liên hệ với nhà cung cấp dịch vụ để mở khóa thiết bị của bạn. + Mã PIN của SIM không chính xác, bạn còn %d lần thử trước khi bạn phải liên hệ với nhà cung cấp dịch vụ để mở khóa thiết bị của bạn. "Thao tác mã PIN của SIM không thành công!" "Trạng thái của máy tính bảng" "Trạng thái điện thoại" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 07b06b7f23e..2b872510298 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -1117,6 +1117,7 @@ SIM 卡 PIN 码不正确,您还可尝试 %d 次。 SIM 卡 PIN 码不正确,您还可尝试 %d 次。如果仍不正确,则需要联系运营商帮您解锁设备。 + SIM 卡 PIN 码不正确,您还可尝试 %d 次。如果仍不正确,则需要联系运营商帮您解锁设备。 "SIM卡PIN码操作失败!" "平板电脑状态" "手机状态" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index 19b85301615..1f3e1ec7a2f 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -1117,6 +1117,7 @@ SIM PIN 碼不正確,您還有 %d 次機會輸入。 SIM PIN 碼不正確,您還有 %d 次機會輸入。如果仍然輸入錯誤,您必須聯絡流動網絡供應商為您的裝置解鎖。 + SIM PIN 碼不正確,您還有 %d 次機會輸入。如果仍然輸入錯誤,您必須聯絡流動網絡供應商為您的裝置解鎖。 "SIM PIN 碼操作失敗!" "平板電腦狀態" "手機狀態" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 8dedff2300e..fe775843d15 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -1117,6 +1117,7 @@ SIM 卡的 PIN 碼輸入錯誤,你還可以再試 %d 次。 SIM 卡的 PIN 碼輸入錯誤,你還可以再試 %d 次。如果仍然失敗,就必須請電信業者為裝置解鎖。 + SIM 卡的 PIN 碼輸入錯誤,你還可以再試 %d 次。如果仍然失敗,就必須請電信業者為裝置解鎖。 "SIM 卡 PIN 碼解鎖失敗!" "平板電腦狀態" "手機狀態" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 6d4380b9efa..7e875372db4 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -1117,6 +1117,7 @@ Ikhodi engalungile yephinikhodi ye-SIM, unemizamo engu-%d esele. Ikhodi engalungile yephinikhodi ye-SIM, unemizamo engu-%d esele. + Ikhodi engalungile yephinikhodi ye-SIM, unemizamo engu-%d esele. "Umsebenzi wephinikhodi ye-SIM wehlulekile!" "Isimo sethebhulethi" "Umumo wefoni" diff --git a/res/values/strings.xml b/res/values/strings.xml index 8a685e6a3f0..bd7ce41c1d1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2611,6 +2611,9 @@ Incorrect SIM PIN code, you have %d remaining attempt before you must contact your carrier to unlock your device. Incorrect SIM PIN code, you have %d remaining attempts. + + Incorrect SIM PIN code, you have %d remaining attempt before you must contact your carrier to unlock your device. SIM PIN operation failed! diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java index 0517b130e1d..71e840d23ae 100644 --- a/src/com/android/settings/IccLockSettings.java +++ b/src/com/android/settings/IccLockSettings.java @@ -487,7 +487,9 @@ public class IccLockSettings extends SettingsPreferenceFragment if (attemptsRemaining == 0) { displayMessage = mRes.getString(R.string.wrong_pin_code_pukked); - } else if (attemptsRemaining > 0) { + } else if (attemptsRemaining == 1) { + displayMessage = mRes.getString(R.string.wrong_pin_code_one, attemptsRemaining); + } else if (attemptsRemaining > 1) { displayMessage = mRes .getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining, attemptsRemaining); From 1c542bbeb01b7c110d3416fcaf6469906cb1977c Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 22 Jan 2018 17:21:58 -0500 Subject: [PATCH 08/20] Remove unused intent. Test: cts-verifier Bug: 63927402 Change-Id: I0ae2f33f7e8f1cce9dbe8166b522eb9cbde4c8b5 --- AndroidManifest.xml | 4 - .../notification/AppNotificationSettings.java | 9 -- .../NotificationSettingsBase.java | 9 +- .../ChannelGroupNotificationSettingsTest.java | 133 ------------------ 4 files changed, 1 insertion(+), 154 deletions(-) delete mode 100644 tests/unit/src/com/android/settings/notification/ChannelGroupNotificationSettingsTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a36ec8ba29c..115d3b483cb 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2714,10 +2714,6 @@ - - - - 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/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/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 - } - } -} From 2607287e5af65d6a758aa60298a03bd7e1044c66 Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 22 Jan 2018 10:30:06 -0500 Subject: [PATCH 09/20] Adding zen dialog in zen settings Test: make ROBOTEST_FILTER=EnableZenModeDialogTest RunSettingsRoboTests -j40 Bug: 63077372 Change-Id: Ib3193788bb0a31e20683d3191eb1238d6a63f1e7 --- res/layout/zen_mode_settings_button.xml | 4 +- res/values/strings.xml | 12 + .../notification/EnableZenModeDialog.java | 467 ++++++++++++++++++ .../ZenModeButtonPreferenceController.java | 14 +- .../notification/ZenModeSettings.java | 9 +- .../notification/EnableZenModeDialogTest.java | 152 ++++++ ...ZenModeButtonPreferenceControllerTest.java | 4 +- 7 files changed, 649 insertions(+), 13 deletions(-) create mode 100644 src/com/android/settings/notification/EnableZenModeDialog.java create mode 100644 tests/robotests/src/com/android/settings/notification/EnableZenModeDialogTest.java 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/strings.xml b/res/values/strings.xml index 448ececf90e..b33c2c94a9d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6845,6 +6845,9 @@ Do Not Disturb + + Turn on Do Not Disturb + Behavior @@ -6902,6 +6905,9 @@ Add + + Turn on + Turn on now @@ -6920,6 +6926,12 @@ Do Not Disturb was automatically turned on by an app (%s) + + Priority only + + + %1$s. %2$s + Work profile sounds 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/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/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/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); From a0aebabdc46766bacddfebf6f59a7a26497b654c Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Tue, 9 Jan 2018 17:36:36 +0000 Subject: [PATCH 10/20] Update string for work mode toggle Bug: 71743500 Test: Go to that page in settings, observe the string. Test: Flip the state, observe the summary is updated. Change-Id: I8b634314c752c6ab8f51e1472db3956194390cbd --- res/values/strings.xml | 10 ++++++---- res/xml/managed_profile_settings.xml | 7 +++---- .../settings/accounts/ManagedProfileSettings.java | 14 ++++++++++---- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 9e113c3bc62..d8625a9af35 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5580,10 +5580,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 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/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; } From 5dd6ed470eb8b3c59c88f605e96789da2f147cc4 Mon Sep 17 00:00:00 2001 From: Carlos Valdivia Date: Tue, 23 Jan 2018 09:12:59 -0800 Subject: [PATCH 11/20] AF/FR Fix onActivityResult condition. Chase list bug. Accidentally bailed on true effectively. Manual tested by going to Settings > System > Erase All Data And then hitting then successfully wiping and reseting. Test: make RunSettingsRoboTests -j40 Bug: 72324059 Change-Id: Ib2a155820811f0ec62a45c1312475c24646ede76 --- src/com/android/settings/MasterClear.java | 9 +++++++-- .../src/com/android/settings/MasterClearTest.java | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index 0e337f68bf0..b7fb69404cd 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -75,7 +75,7 @@ import java.util.List; public class MasterClear extends InstrumentedPreferenceFragment { private static final String TAG = "MasterClear"; - private static final int KEYGUARD_REQUEST = 55; + @VisibleForTesting static final int KEYGUARD_REQUEST = 55; @VisibleForTesting static final int CREDENTIAL_CONFIRM_REQUEST = 56; private static final String KEY_SHOW_ESIM_RESET_CHECKBOX @@ -118,11 +118,16 @@ public class MasterClear extends InstrumentedPreferenceFragment { request, res.getText(R.string.master_clear_title)); } + @VisibleForTesting + boolean isValidRequestCode(int requestCode) { + return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST)); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode != KEYGUARD_REQUEST || requestCode != CREDENTIAL_CONFIRM_REQUEST) { + if (!isValidRequestCode(requestCode)) { return; } diff --git a/tests/robotests/src/com/android/settings/MasterClearTest.java b/tests/robotests/src/com/android/settings/MasterClearTest.java index ac753c109f2..9bf3310c6b8 100644 --- a/tests/robotests/src/com/android/settings/MasterClearTest.java +++ b/tests/robotests/src/com/android/settings/MasterClearTest.java @@ -270,6 +270,13 @@ public class MasterClearTest { assertThat(mMasterClear.tryShowAccountConfirmation()).isTrue(); } + @Test + public void testIsValidRequestCode() { + assertThat(mMasterClear.isValidRequestCode(MasterClear.KEYGUARD_REQUEST)).isTrue(); + assertThat(mMasterClear.isValidRequestCode(MasterClear.CREDENTIAL_CONFIRM_REQUEST)).isTrue(); + assertThat(mMasterClear.isValidRequestCode(0)).isFalse(); + } + private void initScrollView(int height, int scrollY, int childBottom) { when(mScrollView.getHeight()).thenReturn(height); when(mScrollView.getScrollY()).thenReturn(scrollY); From b96ccba79d9f53479c1d4c5cbe35ad13d7e44153 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 23 Jan 2018 10:29:13 -0800 Subject: [PATCH 12/20] Turn on new battery settings page Add priority and shortcut link for new page Bug: 69867246 Test: Manual Change-Id: I887305ee500daa6cd28fd68c6d0ea28869994412 --- AndroidManifest.xml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5a4db20d8aa..f79310b2eee 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2200,12 +2200,15 @@ android:label="@string/power_usage_summary_title" android:icon="@drawable/ic_settings_battery" android:enabled="false"> - - + - + + + + + @@ -2227,15 +2230,10 @@ - + - - - - - From bebea237728607de7b4a57c8fa5622eb56db168e Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 23 Jan 2018 11:11:18 -0800 Subject: [PATCH 13/20] Update strings for suggestions. - modify strings according to the new spec. Bug: 70573674 Test: rebuild Change-Id: I75ac4913c5dc376a542fd2fcec25f8b929fd77ea --- res/values/strings.xml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 164f6d5de29..b75e2ea59c5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1116,14 +1116,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 @@ -2247,7 +2251,7 @@ Turn on Wi-Fi Calling - Use Wi-Fi instead of mobile network + Extend coverage by calling over Wi-Fi Calling preference @@ -6872,11 +6876,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 @@ -8408,7 +8412,7 @@ Set Night Light schedule - Tint screen amber to help you fall asleep + Automatically tint screen every night Night Light is on From 87f35e26bacd752c08aa5a9bc72ae9cca6aaecf0 Mon Sep 17 00:00:00 2001 From: Daniel Nishi Date: Mon, 22 Jan 2018 15:17:25 -0800 Subject: [PATCH 14/20] Migrate functionality to Me Card fragment. This adds in the preferences from the Device Info page into the Me Card page. By overriding the expanded children count, these preferences appear in the the overflow (reflecting their more power user-y status). Bug: 63819909 Test: Manual & Robotest Change-Id: Ic342babfb34db6343b11300eadb30b09a69f56f1 --- AndroidManifest.xml | 4 +- res/values/strings.xml | 11 +- res/xml/me_card.xml | 50 ------ res/xml/my_device_info.xml | 167 ++++++++++++++++++ .../aboutphone/MyDeviceInfoFragment.java} | 55 +++++- .../search/SearchIndexableResourcesImpl.java | 4 +- .../deviceinfo/MyDeviceInfoFragmentTest.java | 100 +++++++++++ 7 files changed, 326 insertions(+), 65 deletions(-) delete mode 100644 res/xml/me_card.xml create mode 100644 res/xml/my_device_info.xml rename src/com/android/settings/{MeCardFragment.java => deviceinfo/aboutphone/MyDeviceInfoFragment.java} (61%) create mode 100644 tests/robotests/src/com/android/settings/deviceinfo/MyDeviceInfoFragmentTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a36ec8ba29c..368183d7496 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1011,7 +1011,7 @@ android:taskAffinity="com.android.settings" android:parentActivityName="Settings"> - + @@ -1027,7 +1027,7 @@ + android:value="com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index eea6dddacde..3ab8e761674 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6437,6 +6437,7 @@ + Account for content @@ -9230,14 +9231,14 @@ - My Phone + My Phone - My Tablet + My Tablet - My Device + My Device - Account + Account - Device name + Device name diff --git a/res/xml/me_card.xml b/res/xml/me_card.xml deleted file mode 100644 index 2d8c21f6073..00000000000 --- a/res/xml/me_card.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - \ 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/src/com/android/settings/MeCardFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java similarity index 61% rename from src/com/android/settings/MeCardFragment.java rename to src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java index 9790fd5e3fb..a3018072c30 100644 --- a/src/com/android/settings/MeCardFragment.java +++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings; +package com.android.settings.deviceinfo.aboutphone; import android.app.Activity; import android.app.Fragment; @@ -23,13 +23,32 @@ 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; @@ -40,10 +59,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class MeCardFragment extends DashboardFragment implements Indexable { +public class MyDeviceInfoFragment extends DashboardFragment { private static final String LOG_TAG = "MeCardFragment"; - private static final String KEY_ME_CARD_HEADER = "me_card_header"; + 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() { @@ -68,7 +88,7 @@ public class MeCardFragment extends DashboardFragment implements Indexable { @Override protected int getPreferenceScreenResId() { - return R.xml.me_card; + return R.xml.my_device_info; } @Override @@ -82,6 +102,21 @@ public class MeCardFragment extends DashboardFragment implements Indexable { 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; } @@ -89,7 +124,7 @@ public class MeCardFragment extends DashboardFragment implements Indexable { private void initHeader() { // TODO: Migrate into its own controller. final LayoutPreference headerPreference = - (LayoutPreference) getPreferenceScreen().findPreference(KEY_ME_CARD_HEADER); + (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(); @@ -123,7 +158,7 @@ public class MeCardFragment extends DashboardFragment implements Indexable { public List getXmlResourcesToIndex( Context context, boolean enabled) { final SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = R.xml.me_card; + sir.xmlResId = R.xml.my_device_info; return Arrays.asList(sir); } @@ -133,5 +168,13 @@ public class MeCardFragment extends DashboardFragment implements Indexable { 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/search/SearchIndexableResourcesImpl.java b/src/com/android/settings/search/SearchIndexableResourcesImpl.java index 4067e6b69af..034cbd0e06b 100644 --- a/src/com/android/settings/search/SearchIndexableResourcesImpl.java +++ b/src/com/android/settings/search/SearchIndexableResourcesImpl.java @@ -21,7 +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.MeCardFragment; +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; @@ -174,7 +174,7 @@ public class SearchIndexableResourcesImpl implements SearchIndexableResources { addIndex(ZenModeAutomationSettings.class); addIndex(NightDisplaySettings.class); addIndex(SmartBatterySettings.class); - addIndex(MeCardFragment.class); + addIndex(MyDeviceInfoFragment.class); } @Override 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); + } +} From 5ac5b85d2a8376f1b4dda7c9dfa0a33fe70b5aa0 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 22 Jan 2018 17:48:04 -0800 Subject: [PATCH 15/20] Add icon in RestrictedAppDetails use preference_app to make sure the app icon is 24dp. Bug: 71502850 Test: Screenshot | RunSettingsRoboTests Change-Id: I0d7704246299201e72b991f199f9ddd2f224a8a3 --- .../fuelgauge/RestrictedAppDetails.java | 12 ++--- .../widget/AppCheckBoxPreference.java | 38 +++++++++++++ .../fuelgauge/RestrictedAppDetailsTest.java | 4 ++ .../widget/AppCheckBoxPreferenceTest.java | 53 +++++++++++++++++++ 4 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 src/com/android/settings/widget/AppCheckBoxPreference.java create mode 100644 tests/robotests/src/com/android/settings/widget/AppCheckBoxPreferenceTest.java 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/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/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/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); + } +} From db9a9bb0b0438816909e002156cb7322c66a0d28 Mon Sep 17 00:00:00 2001 From: yuemingw Date: Tue, 23 Jan 2018 18:58:38 +0000 Subject: [PATCH 16/20] Fix the crash when editing or deleting APN. Previously ApnPrefence starts an intent with URI content://telephony/carrier/filtered/id, this URI is only implemented in telephony provider for query(), but not for delete() or update(). This caused the crash when user tries to update or delete an APN through this URI. We should let ApnPrefence starts an intent with URI content://telephony/carrier/id, which is a general telephony URI and all of query() add() delete() update() are implemented for non-DPC mode. And let ApnPrefence starts an intent with URI content://telephony/carrier/filtered/id in DPC mode(as the general URI can't access DPC-owned APN), when only DPC-owned APNs are presented to user. In the DPC mode, user can't update, add or delete an APN, they can only view(query) the APN, so it won't crash with URI_FILTERED. Bug: 72387301 Test: manual. Tried update, delete, and add action in Access Point Name page, no crash occurs. Instrumentation test will be added in b/72154761. Change-Id: I9979cbffcc94a37b2bd96db766ececd0ac7b20e2 --- src/com/android/settings/ApnPreference.java | 9 ++++++++- src/com/android/settings/ApnSettings.java | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) 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; } From 5f67c6491fff278e79b5bcc34cab71152df43b4d Mon Sep 17 00:00:00 2001 From: Chad Brubaker Date: Thu, 11 Jan 2018 11:09:31 -0800 Subject: [PATCH 17/20] Expose lockdown button in settings Test: manually verified toggle controls lockdown button visibility Test: make RunSettingsRoboTests Bug: 37221346 Change-Id: Ie59f9c6a1df1d877b18986c6a58492a5aa789d34 --- res/values/strings.xml | 4 + res/xml/security_lockscreen_settings.xml | 5 + .../LockdownButtonPreferenceController.java | 61 ++++++++++++ .../security/LockscreenDashboardFragment.java | 3 + ...ockdownButtonPreferenceControllerTest.java | 98 +++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 src/com/android/settings/security/LockdownButtonPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/security/LockdownButtonPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 74737ad05c7..4aefa7eec26 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -777,6 +777,10 @@ Enable widgets Disabled by admin + + Show lockdown option + + Display power button option that turns off extended access and fingerprint unlocking. None 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/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/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); + } +} From 5bc07cfc4c5a6c280c6b64a09de5b199611c1044 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 22 Jan 2018 10:26:29 -0800 Subject: [PATCH 18/20] Added activity for the ACTION_DIRECTORY_ACCESS_SETTINGS intent. Applications typically use this action to ask the user to revert the "Do not ask again" status of directory access requested made by StorageVolume.createAccessIntent(directory). Test: adb shell am start -a android.settings.DIRECTORY_ACCESS_SETTINGS Test: atest CtsAppSecurityHostTestCases:ScopedDirectoryAccessTest#testResetDoNotAskAgain Bug: 63720392 Change-Id: Ib9007c2c08a75e2e54df6d6b5f465f9c3ccc82be --- AndroidManifest.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a36ec8ba29c..6621dc105db 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3358,6 +3358,17 @@ android:value="true" /> + + + + + + + + From 372789b46dd61296758fbd64bebdae59c68e8c71 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 23 Jan 2018 14:26:30 -0800 Subject: [PATCH 19/20] Remove all gesture suggestions They are already removed from first 14 days category, now removing permanently. Change-Id: I1740d3bff59ff56142c10cce3b9617009c58f47d Fixes: 72224790 Test: robotests --- AndroidManifest.xml | 93 ------------------- res/values/strings.xml | 19 ---- src/com/android/settings/Settings.java | 11 +-- .../SuggestionFeatureProviderImpl.java | 25 ----- 4 files changed, 1 insertion(+), 147 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a36ec8ba29c..6ecc77d6923 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3228,99 +3228,6 @@ android:permission="android.permission.DUMP" android:enabled="@bool/config_has_help" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Open camera quickly - - Press power button twice to open camera - Flip camera @@ -8852,13 +8849,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 @@ -8869,9 +8859,6 @@ To check time, notification icons, and other info, double-tap your screen. - - Check notifications when screen is off - Lift to check phone @@ -8886,9 +8873,6 @@ To check time, notification icons, and other info, pick up your device. - - Check notifications when screen is off - Swipe fingerprint for notifications @@ -8904,9 +8888,6 @@ See notifications quickly - - Swipe down on the fingerprint sensor - diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 4503b641988..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; @@ -134,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/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; } From 1e141fb670577a03bafb5a90bd3c198f449d2ce2 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 23 Jan 2018 14:42:30 -0800 Subject: [PATCH 20/20] Tweak master switch's right padding Change-Id: I635833fc61fd3e644d2f606bd78efc9d159a09de Fixes: 69462439 Test: visual --- res/values/dimens.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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