From e10875210adef2949aa4f9b56c40fb6d81b0f222 Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Mon, 5 Mar 2018 16:13:33 +0000 Subject: [PATCH 01/17] Fix SettingsRoboTests build error with OpenJDK 9 - CL http://ag/3671365 broke git_pi-release/marlin-userdebug-jdk9 when building "m checkbuild". It shouldn't break the image build Test: m SettingsRoboTests ROBOTEST_FILTER=com.android.settings.datetime.timezone EXPERIMENTAL_USE_OPENJDK9=true USE_R8=true Change-Id: I161c0350cff55bd13ba4a6c63df4e4e9bc4b1a5f --- .../timezone/model/TimeZoneDataTest.java | 6 +-- .../src/libcore/util/CountryTimeZones.java | 45 ------------------- .../src/libcore/util/CountryZonesFinder.java | 38 ---------------- 3 files changed, 3 insertions(+), 86 deletions(-) delete mode 100644 tests/robotests/src/libcore/util/CountryTimeZones.java delete mode 100644 tests/robotests/src/libcore/util/CountryZonesFinder.java diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java index e6073a8ecb4..4a624b70f6c 100644 --- a/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java +++ b/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java @@ -73,13 +73,13 @@ public class TimeZoneDataTest { CountryTimeZones US = mock(CountryTimeZones.class); when(US.getCountryIso()).thenReturn("us"); when(US.getTimeZoneMappings()).thenReturn(Arrays.asList( - new CountryTimeZones.TimeZoneMapping("Unknown/Secret_City", true), - new CountryTimeZones.TimeZoneMapping("Unknown/Secret_City2", false) + TimeZoneMapping.createForTests("Unknown/Secret_City", true), + TimeZoneMapping.createForTests("Unknown/Secret_City2", false) )); CountryTimeZones GB = mock(CountryTimeZones.class); when(GB.getCountryIso()).thenReturn("gb"); when(GB.getTimeZoneMappings()).thenReturn(Collections.singletonList( - new TimeZoneMapping("Unknown/Secret_City", true) + TimeZoneMapping.createForTests("Unknown/Secret_City", true) )); when(mCountryZonesFinder.lookupCountryTimeZonesForZoneId("Unknown/Secret_City")) .thenReturn(Arrays.asList(US, GB)); diff --git a/tests/robotests/src/libcore/util/CountryTimeZones.java b/tests/robotests/src/libcore/util/CountryTimeZones.java deleted file mode 100644 index 2087848609d..00000000000 --- a/tests/robotests/src/libcore/util/CountryTimeZones.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 libcore.util; - -import java.util.List; - -/** - * Empty implementation of CountryTimeZones for Robolectric test. - */ -public class CountryTimeZones { - public CountryTimeZones() { - } - - public final static class TimeZoneMapping { - public final String timeZoneId; - public final boolean showInPicker; - - public TimeZoneMapping(String timeZoneId, boolean showInPicker) { - this.timeZoneId = timeZoneId; - this.showInPicker = showInPicker; - } - } - - public List getTimeZoneMappings() { - return null; - } - - public String getCountryIso() { - return null; - } -} diff --git a/tests/robotests/src/libcore/util/CountryZonesFinder.java b/tests/robotests/src/libcore/util/CountryZonesFinder.java deleted file mode 100644 index 51149ecb915..00000000000 --- a/tests/robotests/src/libcore/util/CountryZonesFinder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 libcore.util; - -import java.util.List; - -/** - * Empty implementation of CountryZonesFinder for Robolectric test. - */ -public class CountryZonesFinder { - public CountryZonesFinder(List countryTimeZonesList) {} - - public List lookupAllCountryIsoCodes() { - return null; - } - - public List lookupCountryTimeZonesForZoneId(String zoneId) { - return null; - } - - public CountryTimeZones lookupCountryTimeZones(String countryIso) { - return null; - } -} From e35badd9d549500230db943b1b0880ab7198a4e2 Mon Sep 17 00:00:00 2001 From: arangelov Date: Fri, 27 Oct 2017 13:51:28 +0100 Subject: [PATCH 02/17] Make "Action not allowed" dialog compliant with Material Spec and replace full screen activities with the dialog. Bug: 64893948 Test: make ROBOTEST_FILTER=ActionDisabledByAdminDialogTest -j40 RunSettingsRoboTests Test: make ROBOTEST_FILTER=ActionDisabledByAdminDialogHelperTest -j40 RunSettingsRoboTests Change-Id: I9308d8d86a3789b8f2c92b9f4f20cf00cce71d14 --- AndroidManifest.xml | 2 +- res/layout/admin_support_details_content.xml | 34 --- res/layout/admin_support_details_dialog.xml | 11 +- .../admin_support_details_empty_view.xml | 26 -- res/layout/preference_list_fragment.xml | 2 - res/values/dimens.xml | 4 + src/com/android/settings/MasterClear.java | 10 +- .../android/settings/MasterClearConfirm.java | 14 +- src/com/android/settings/ResetNetwork.java | 10 +- .../android/settings/ResetNetworkConfirm.java | 11 +- .../settings/RestrictedSettingsFragment.java | 21 +- .../ShowAdminSupportDetailsDialog.java | 222 ------------------ .../RestrictedDashboardFragment.java | 21 +- .../ActionDisabledByAdminDialog.java | 75 ++++++ .../ActionDisabledByAdminDialogHelper.java | 204 ++++++++++++++++ src/com/android/settings/enterprise/OWNERS | 3 + src/com/android/settings/nfc/AndroidBeam.java | 9 +- ...ActionDisabledByAdminDialogHelperTest.java | 186 +++++++++++++++ .../ActionDisabledByAdminDialogTest.java | 75 ++++++ .../settings/testutils/CustomActivity.java | 26 ++ .../testutils/shadow/ShadowActivity.java | 35 +++ .../shadow/ShadowDevicePolicyManager.java | 41 ++++ .../testutils/shadow/ShadowProcess.java | 18 ++ .../testutils/shadow/ShadowUserManager.java | 10 +- 24 files changed, 735 insertions(+), 335 deletions(-) delete mode 100644 res/layout/admin_support_details_content.xml delete mode 100644 res/layout/admin_support_details_empty_view.xml delete mode 100644 src/com/android/settings/ShowAdminSupportDetailsDialog.java create mode 100644 src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java create mode 100644 src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java create mode 100644 tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java create mode 100644 tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogTest.java create mode 100644 tests/robotests/src/com/android/settings/testutils/CustomActivity.java create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivity.java create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowProcess.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dbe231b5255..feb1b05f32a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2955,7 +2955,7 @@ android:value="com.android.settings.applications.appinfo.ExternalSourcesDetails" /> - diff --git a/res/layout/admin_support_details_content.xml b/res/layout/admin_support_details_content.xml deleted file mode 100644 index 7c756c4d397..00000000000 --- a/res/layout/admin_support_details_content.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/admin_support_details_dialog.xml b/res/layout/admin_support_details_dialog.xml index c83add323f9..7de91d0d939 100644 --- a/res/layout/admin_support_details_dialog.xml +++ b/res/layout/admin_support_details_dialog.xml @@ -23,7 +23,7 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" - android:paddingBottom="@dimen/admin_details_dialog_padding"> + android:paddingBottom="@dimen/admin_details_dialog_title_bottom_padding"> - + diff --git a/res/layout/admin_support_details_empty_view.xml b/res/layout/admin_support_details_empty_view.xml deleted file mode 100644 index bbbbbbcbc7c..00000000000 --- a/res/layout/admin_support_details_empty_view.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/preference_list_fragment.xml b/res/layout/preference_list_fragment.xml index b84aa3822ba..eeea9c492f1 100644 --- a/res/layout/preference_list_fragment.xml +++ b/res/layout/preference_list_fragment.xml @@ -62,8 +62,6 @@ android:gravity="center_vertical" android:visibility="gone" /> - - 24dp + 20dp 48dp 36dp + 24dp + 8dp + 88dp 8dp diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index 9ad32e267d2..46e0d41cfac 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -57,6 +57,7 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.SubSettingLauncher; +import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ConfirmLockPattern; import com.android.settingslib.RestrictedLockUtils; @@ -503,10 +504,11 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL if (disallow && !Utils.isDemoUser(context)) { return inflater.inflate(R.layout.master_clear_disallowed_screen, null); } else if (admin != null) { - View view = inflater.inflate(R.layout.admin_support_details_empty_view, null); - ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), view, admin, false); - view.setVisibility(View.VISIBLE); - return view; + new ActionDisabledByAdminDialogHelper(getActivity()) + .prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin) + .setOnDismissListener(__ -> getActivity().finish()) + .show(); + return new View(getContext()); } mContentView = inflater.inflate(R.layout.master_clear, null); diff --git a/src/com/android/settings/MasterClearConfirm.java b/src/com/android/settings/MasterClearConfirm.java index 59736fd4cf5..a92c8f85c7e 100644 --- a/src/com/android/settings/MasterClearConfirm.java +++ b/src/com/android/settings/MasterClearConfirm.java @@ -34,6 +34,7 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.core.InstrumentedFragment; +import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settingslib.RestrictedLockUtils; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -151,10 +152,11 @@ public class MasterClearConfirm extends InstrumentedFragment { UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId())) { return inflater.inflate(R.layout.master_clear_disallowed_screen, null); } else if (admin != null) { - View view = inflater.inflate(R.layout.admin_support_details_empty_view, null); - ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), view, admin, false); - view.setVisibility(View.VISIBLE); - return view; + new ActionDisabledByAdminDialogHelper(getActivity()) + .prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin) + .setOnDismissListener(__ -> getActivity().finish()) + .show(); + return new View(getActivity()); } mContentView = inflater.inflate(R.layout.master_clear_confirm, null); establishFinalConfirmationState(); @@ -167,9 +169,9 @@ public class MasterClearConfirm extends InstrumentedFragment { TextView confirmationMessage = (TextView) mContentView.findViewById(R.id.master_clear_confirm); if (confirmationMessage != null) { - String accessibileText = new StringBuilder(currentTitle).append(",").append( + String accessibleText = new StringBuilder(currentTitle).append(",").append( confirmationMessage.getText()).toString(); - getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibileText)); + getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText)); } } diff --git a/src/com/android/settings/ResetNetwork.java b/src/com/android/settings/ResetNetwork.java index 8043750a743..591ce0ab689 100644 --- a/src/com/android/settings/ResetNetwork.java +++ b/src/com/android/settings/ResetNetwork.java @@ -45,6 +45,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.PhoneConstants; import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.SubSettingLauncher; +import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ConfirmLockPattern; import com.android.settingslib.RestrictedLockUtils; @@ -245,10 +246,11 @@ public class ResetNetwork extends InstrumentedFragment { UserManager.DISALLOW_NETWORK_RESET, UserHandle.myUserId())) { return inflater.inflate(R.layout.network_reset_disallowed_screen, null); } else if (admin != null) { - View view = inflater.inflate(R.layout.admin_support_details_empty_view, null); - ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), view, admin, false); - view.setVisibility(View.VISIBLE); - return view; + new ActionDisabledByAdminDialogHelper(getActivity()) + .prepareDialogBuilder(UserManager.DISALLOW_NETWORK_RESET, admin) + .setOnDismissListener(__ -> getActivity().finish()) + .show(); + return new View(getContext()); } mContentView = inflater.inflate(R.layout.reset_network, null); diff --git a/src/com/android/settings/ResetNetworkConfirm.java b/src/com/android/settings/ResetNetworkConfirm.java index edded11c8fc..d735c0666a2 100644 --- a/src/com/android/settings/ResetNetworkConfirm.java +++ b/src/com/android/settings/ResetNetworkConfirm.java @@ -27,7 +27,6 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Bundle; -import android.os.RecoverySystem; import android.os.UserHandle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; @@ -42,6 +41,7 @@ import android.widget.Toast; import com.android.ims.ImsManager; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.PhoneConstants; +import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settings.wrapper.RecoverySystemWrapper; import com.android.settings.core.InstrumentedFragment; import com.android.settingslib.RestrictedLockUtils; @@ -197,10 +197,11 @@ public class ResetNetworkConfirm extends InstrumentedFragment { UserManager.DISALLOW_NETWORK_RESET, UserHandle.myUserId())) { return inflater.inflate(R.layout.network_reset_disallowed_screen, null); } else if (admin != null) { - View view = inflater.inflate(R.layout.admin_support_details_empty_view, null); - ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), view, admin, false); - view.setVisibility(View.VISIBLE); - return view; + new ActionDisabledByAdminDialogHelper(getActivity()) + .prepareDialogBuilder(UserManager.DISALLOW_NETWORK_RESET, admin) + .setOnDismissListener(__ -> getActivity().finish()) + .show(); + return new View(getContext()); } mContentView = inflater.inflate(R.layout.reset_network_confirm, null); establishFinalConfirmationState(); diff --git a/src/com/android/settings/RestrictedSettingsFragment.java b/src/com/android/settings/RestrictedSettingsFragment.java index bbb317ba420..b17ca84650f 100644 --- a/src/com/android/settings/RestrictedSettingsFragment.java +++ b/src/com/android/settings/RestrictedSettingsFragment.java @@ -17,6 +17,7 @@ package com.android.settings; import android.app.Activity; +import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -30,6 +31,7 @@ import android.view.View; import android.widget.TextView; import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settingslib.RestrictedLockUtils; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -67,7 +69,6 @@ public abstract class RestrictedSettingsFragment extends SettingsPreferenceFragm private RestrictionsManager mRestrictionsManager; private final String mRestrictionKey; - private View mAdminSupportDetails; private EnforcedAdmin mEnforcedAdmin; private TextView mEmptyTextView; @@ -85,6 +86,8 @@ public abstract class RestrictedSettingsFragment extends SettingsPreferenceFragm } }; + private AlertDialog mActionDisabledDialog; + /** * @param restrictionKey The restriction key to check before pin protecting * this settings page. Pass in {@link RESTRICT_IF_OVERRIDABLE} if it should @@ -116,7 +119,6 @@ public abstract class RestrictedSettingsFragment extends SettingsPreferenceFragm @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mAdminSupportDetails = initAdminSupportDetailsView(); mEmptyTextView = initEmptyTextView(); } @@ -204,10 +206,6 @@ public abstract class RestrictedSettingsFragment extends SettingsPreferenceFragm return restricted && mRestrictionsManager.hasRestrictionsProvider(); } - private View initAdminSupportDetailsView() { - return getActivity().findViewById(R.id.admin_support_details); - } - protected TextView initEmptyTextView() { TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty); return emptyView; @@ -229,11 +227,14 @@ public abstract class RestrictedSettingsFragment extends SettingsPreferenceFragm @Override protected void onDataSetChanged() { highlightPreferenceIfNeeded(); - if (mAdminSupportDetails != null && isUiRestrictedByOnlyAdmin()) { + if (isUiRestrictedByOnlyAdmin() + && (mActionDisabledDialog == null || !mActionDisabledDialog.isShowing())) { final EnforcedAdmin admin = getRestrictionEnforcedAdmin(); - ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), - mAdminSupportDetails, admin, false); - setEmptyView(mAdminSupportDetails); + mActionDisabledDialog = new ActionDisabledByAdminDialogHelper(getActivity()) + .prepareDialogBuilder(mRestrictionKey, admin) + .setOnDismissListener(__ -> getActivity().finish()) + .show(); + setEmptyView(new View(getContext())); } else if (mEmptyTextView != null) { setEmptyView(mEmptyTextView); } diff --git a/src/com/android/settings/ShowAdminSupportDetailsDialog.java b/src/com/android/settings/ShowAdminSupportDetailsDialog.java deleted file mode 100644 index 321f93d10c3..00000000000 --- a/src/com/android/settings/ShowAdminSupportDetailsDialog.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2015 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.AlertDialog; -import android.app.AppGlobals; -import android.app.admin.DevicePolicyManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Process; -import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; - -import java.util.Objects; - -public class ShowAdminSupportDetailsDialog extends Activity - implements DialogInterface.OnDismissListener { - - private static final String TAG = "AdminSupportDialog"; - - private EnforcedAdmin mEnforcedAdmin; - private View mDialogView; - private String mRestriction = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mEnforcedAdmin = getAdminDetailsFromIntent(getIntent()); - mRestriction = getRestrictionFromIntent(getIntent()); - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - mDialogView = LayoutInflater.from(builder.getContext()).inflate( - R.layout.admin_support_details_dialog, null); - initializeDialogViews(mDialogView, mEnforcedAdmin.component, mEnforcedAdmin.userId, - mRestriction); - builder.setOnDismissListener(this) - .setPositiveButton(R.string.okay, null) - .setView(mDialogView) - .show(); - } - - @Override - public void onNewIntent(Intent intent) { - super.onNewIntent(intent); - EnforcedAdmin admin = getAdminDetailsFromIntent(intent); - String restriction = getRestrictionFromIntent(intent); - if (!mEnforcedAdmin.equals(admin) || !Objects.equals(mRestriction, restriction)) { - mEnforcedAdmin = admin; - mRestriction = restriction; - initializeDialogViews(mDialogView, mEnforcedAdmin.component, mEnforcedAdmin.userId, - mRestriction); - } - } - - private EnforcedAdmin getAdminDetailsFromIntent(Intent intent) { - EnforcedAdmin admin = new EnforcedAdmin(null, UserHandle.myUserId()); - if (intent == null) { - return admin; - } - admin.component = intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN); - admin.userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); - return admin; - } - - private String getRestrictionFromIntent(Intent intent) { - if (intent == null) return null; - return intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION); - } - - private void initializeDialogViews(View root, ComponentName admin, int userId, - String restriction) { - if (admin != null) { - if (!RestrictedLockUtils.isAdminInCurrentUserOrProfile(this, admin) - || !RestrictedLockUtils.isCurrentUserOrProfile(this, userId)) { - admin = null; - } else { - ActivityInfo ai = null; - try { - ai = AppGlobals.getPackageManager().getReceiverInfo(admin, 0 /* flags */, - userId); - } catch (RemoteException e) { - Log.w(TAG, "Missing reciever info", e); - } - if (ai != null) { - Drawable icon = ai.loadIcon(getPackageManager()); - Drawable badgedIcon = getPackageManager().getUserBadgedIcon( - icon, new UserHandle(userId)); - ((ImageView) root.findViewById(R.id.admin_support_icon)).setImageDrawable( - badgedIcon); - } - } - } - - setAdminSupportTitle(root, restriction); - setAdminSupportDetails(this, root, new EnforcedAdmin(admin, userId), true); - } - - private void setAdminSupportTitle(View root, String restriction) { - final TextView titleView = (TextView) root.findViewById(R.id.admin_support_dialog_title); - if (titleView == null) { - return; - } - if (restriction == null) { - titleView.setText(R.string.disabled_by_policy_title); - return; - } - switch(restriction) { - case UserManager.DISALLOW_ADJUST_VOLUME: - titleView.setText(R.string.disabled_by_policy_title_adjust_volume); - break; - case UserManager.DISALLOW_OUTGOING_CALLS: - titleView.setText(R.string.disabled_by_policy_title_outgoing_calls); - break; - case UserManager.DISALLOW_SMS: - titleView.setText(R.string.disabled_by_policy_title_sms); - break; - case DevicePolicyManager.POLICY_DISABLE_CAMERA: - titleView.setText(R.string.disabled_by_policy_title_camera); - break; - case DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE: - titleView.setText(R.string.disabled_by_policy_title_screen_capture); - break; - case DevicePolicyManager.POLICY_MANDATORY_BACKUPS: - titleView.setText(R.string.disabled_by_policy_title_turn_off_backups); - break; - default: - // Use general text if no specialized title applies - titleView.setText(R.string.disabled_by_policy_title); - } - } - - public static void setAdminSupportDetails(final Activity activity, View root, - final EnforcedAdmin enforcedAdmin, final boolean finishActivity) { - if (enforcedAdmin == null) { - return; - } - - if (enforcedAdmin.component != null) { - DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService( - Context.DEVICE_POLICY_SERVICE); - if (!RestrictedLockUtils.isAdminInCurrentUserOrProfile(activity, - enforcedAdmin.component) || !RestrictedLockUtils.isCurrentUserOrProfile( - activity, enforcedAdmin.userId)) { - enforcedAdmin.component = null; - } else { - if (enforcedAdmin.userId == UserHandle.USER_NULL) { - enforcedAdmin.userId = UserHandle.myUserId(); - } - CharSequence supportMessage = null; - if (UserHandle.isSameApp(Process.myUid(), Process.SYSTEM_UID)) { - supportMessage = dpm.getShortSupportMessageForUser( - enforcedAdmin.component, enforcedAdmin.userId); - } - if (supportMessage != null) { - TextView textView = (TextView) root.findViewById(R.id.admin_support_msg); - textView.setText(supportMessage); - } - } - } - - root.findViewById(R.id.admins_policies_list).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(); - if (enforcedAdmin.component != null) { - intent.setClass(activity, DeviceAdminAdd.class); - intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, - enforcedAdmin.component); - intent.putExtra(DeviceAdminAdd.EXTRA_CALLED_FROM_SUPPORT_DIALOG, true); - // DeviceAdminAdd class may need to run as managed profile. - activity.startActivityAsUser(intent, - new UserHandle(enforcedAdmin.userId)); - } else { - intent.setClass(activity, Settings.DeviceAdminSettingsActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Activity merges both managed profile and parent users - // admins so show as same user as this activity. - activity.startActivity(intent); - } - if (finishActivity) { - activity.finish(); - } - } - }); - } - - @Override - public void onDismiss(DialogInterface dialog) { - finish(); - } -} diff --git a/src/com/android/settings/dashboard/RestrictedDashboardFragment.java b/src/com/android/settings/dashboard/RestrictedDashboardFragment.java index 99aaff433a4..de0b39667de 100644 --- a/src/com/android/settings/dashboard/RestrictedDashboardFragment.java +++ b/src/com/android/settings/dashboard/RestrictedDashboardFragment.java @@ -19,6 +19,7 @@ package com.android.settings.dashboard; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.Activity; +import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -31,9 +32,9 @@ import android.os.UserManager; import android.view.View; import android.widget.TextView; +import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settings.R; import com.android.settings.RestrictedSettingsFragment; -import com.android.settings.ShowAdminSupportDetailsDialog; import com.android.settingslib.RestrictedLockUtils; /** @@ -69,7 +70,6 @@ public abstract class RestrictedDashboardFragment extends DashboardFragment { private RestrictionsManager mRestrictionsManager; private final String mRestrictionKey; - private View mAdminSupportDetails; private EnforcedAdmin mEnforcedAdmin; private TextView mEmptyTextView; @@ -86,6 +86,7 @@ public abstract class RestrictedDashboardFragment extends DashboardFragment { } } }; + private AlertDialog mActionDisabledDialog; /** * @param restrictionKey The restriction key to check before pin protecting @@ -118,7 +119,6 @@ public abstract class RestrictedDashboardFragment extends DashboardFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mAdminSupportDetails = initAdminSupportDetailsView(); mEmptyTextView = initEmptyTextView(); } @@ -206,10 +206,6 @@ public abstract class RestrictedDashboardFragment extends DashboardFragment { return restricted && mRestrictionsManager.hasRestrictionsProvider(); } - private View initAdminSupportDetailsView() { - return getActivity().findViewById(R.id.admin_support_details); - } - protected TextView initEmptyTextView() { TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty); return emptyView; @@ -231,11 +227,14 @@ public abstract class RestrictedDashboardFragment extends DashboardFragment { @Override protected void onDataSetChanged() { highlightPreferenceIfNeeded(); - if (mAdminSupportDetails != null && isUiRestrictedByOnlyAdmin()) { + if (isUiRestrictedByOnlyAdmin() + && (mActionDisabledDialog == null || !mActionDisabledDialog.isShowing())) { final EnforcedAdmin admin = getRestrictionEnforcedAdmin(); - ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), - mAdminSupportDetails, admin, false); - setEmptyView(mAdminSupportDetails); + mActionDisabledDialog = new ActionDisabledByAdminDialogHelper(getActivity()) + .prepareDialogBuilder(mRestrictionKey, admin) + .setOnDismissListener(__ -> getActivity().finish()) + .show(); + setEmptyView(new View(getContext())); } else if (mEmptyTextView != null) { setEmptyView(mEmptyTextView); } diff --git a/src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java b/src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java new file mode 100644 index 00000000000..37c51248873 --- /dev/null +++ b/src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java @@ -0,0 +1,75 @@ +/* + * 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.enterprise; + +import android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +public class ActionDisabledByAdminDialog extends Activity + implements DialogInterface.OnDismissListener { + + private ActionDisabledByAdminDialogHelper mDialogHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final RestrictedLockUtils.EnforcedAdmin enforcedAdmin = + getAdminDetailsFromIntent(getIntent()); + final String restriction = getRestrictionFromIntent(getIntent()); + mDialogHelper = new ActionDisabledByAdminDialogHelper(this); + mDialogHelper.prepareDialogBuilder(restriction, enforcedAdmin) + .setOnDismissListener(this) + .show(); + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + final EnforcedAdmin admin = getAdminDetailsFromIntent(intent); + final String restriction = getRestrictionFromIntent(intent); + mDialogHelper.updateDialog(restriction, admin); + } + + @android.support.annotation.VisibleForTesting + EnforcedAdmin getAdminDetailsFromIntent(Intent intent) { + final EnforcedAdmin admin = new EnforcedAdmin(null, UserHandle.myUserId()); + if (intent == null) { + return admin; + } + admin.component = intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN); + admin.userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); + return admin; + } + + @android.support.annotation.VisibleForTesting + String getRestrictionFromIntent(Intent intent) { + if (intent == null) return null; + return intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION); + } + + @Override + public void onDismiss(DialogInterface dialog) { + finish(); + } +} diff --git a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java new file mode 100644 index 00000000000..3429957b016 --- /dev/null +++ b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java @@ -0,0 +1,204 @@ +/* + * 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.enterprise; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AppGlobals; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.graphics.drawable.Drawable; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.settings.DeviceAdminAdd; +import com.android.settings.R; +import com.android.settings.Settings; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +import java.util.Objects; + +/** + * Helper class for {@link ActionDisabledByAdminDialog} which sets up the dialog. + */ +public class ActionDisabledByAdminDialogHelper { + + private static final String TAG = ActionDisabledByAdminDialogHelper.class.getName(); + private EnforcedAdmin mEnforcedAdmin; + private ViewGroup mDialogView; + private String mRestriction = null; + private Activity mActivity; + + public ActionDisabledByAdminDialogHelper(Activity activity) { + mActivity = activity; + } + + public AlertDialog.Builder prepareDialogBuilder(String restriction, + EnforcedAdmin enforcedAdmin) { + mEnforcedAdmin = enforcedAdmin; + mRestriction = restriction; + + final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + mDialogView = (ViewGroup) LayoutInflater.from(builder.getContext()).inflate( + R.layout.admin_support_details_dialog, null); + initializeDialogViews(mDialogView, mEnforcedAdmin.component, mEnforcedAdmin.userId, + mRestriction); + return builder + .setPositiveButton(R.string.okay, null) + .setNeutralButton(R.string.admin_more_details, + (dialog, which) -> { + showAdminPolicies(mEnforcedAdmin, mActivity); + mActivity.finish(); + }) + .setView(mDialogView); + } + + public void updateDialog(String restriction, EnforcedAdmin admin) { + if (mEnforcedAdmin.equals(admin) && Objects.equals(mRestriction, restriction)) { + return; + } + mEnforcedAdmin = admin; + mRestriction = restriction; + initializeDialogViews(mDialogView, mEnforcedAdmin.component, mEnforcedAdmin.userId, + mRestriction); + } + + private void initializeDialogViews(View root, ComponentName admin, int userId, + String restriction) { + if (admin == null) { + return; + } + if (!RestrictedLockUtils.isAdminInCurrentUserOrProfile(mActivity, admin) + || !RestrictedLockUtils.isCurrentUserOrProfile(mActivity, userId)) { + admin = null; + } else { + ActivityInfo ai = null; + try { + ai = AppGlobals.getPackageManager().getReceiverInfo(admin, 0 /* flags */, + userId); + } catch (RemoteException e) { + Log.w(TAG, "Missing reciever info", e); + } + if (ai != null) { + final Drawable icon = ai.loadIcon(mActivity.getPackageManager()); + final Drawable badgedIcon = mActivity.getPackageManager().getUserBadgedIcon( + icon, new UserHandle(userId)); + ((ImageView) root.findViewById(R.id.admin_support_icon)).setImageDrawable( + badgedIcon); + } + } + + setAdminSupportTitle(root, restriction); + setAdminSupportDetails(mActivity, root, new EnforcedAdmin(admin, userId)); + } + + @VisibleForTesting + void setAdminSupportTitle(View root, String restriction) { + final TextView titleView = root.findViewById(R.id.admin_support_dialog_title); + if (titleView == null) { + return; + } + if (restriction == null) { + titleView.setText(R.string.disabled_by_policy_title); + return; + } + switch (restriction) { + case UserManager.DISALLOW_ADJUST_VOLUME: + titleView.setText(R.string.disabled_by_policy_title_adjust_volume); + break; + case UserManager.DISALLOW_OUTGOING_CALLS: + titleView.setText(R.string.disabled_by_policy_title_outgoing_calls); + break; + case UserManager.DISALLOW_SMS: + titleView.setText(R.string.disabled_by_policy_title_sms); + break; + case DevicePolicyManager.POLICY_DISABLE_CAMERA: + titleView.setText(R.string.disabled_by_policy_title_camera); + break; + case DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE: + titleView.setText(R.string.disabled_by_policy_title_screen_capture); + break; + case DevicePolicyManager.POLICY_MANDATORY_BACKUPS: + titleView.setText(R.string.disabled_by_policy_title_turn_off_backups); + break; + default: + // Use general text if no specialized title applies + titleView.setText(R.string.disabled_by_policy_title); + } + } + + @VisibleForTesting + void setAdminSupportDetails(final Activity activity, final View root, + final EnforcedAdmin enforcedAdmin) { + if (enforcedAdmin == null || enforcedAdmin.component == null) { + return; + } + final DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (!RestrictedLockUtils.isAdminInCurrentUserOrProfile(activity, + enforcedAdmin.component) || !RestrictedLockUtils.isCurrentUserOrProfile( + activity, enforcedAdmin.userId)) { + enforcedAdmin.component = null; + } else { + if (enforcedAdmin.userId == UserHandle.USER_NULL) { + enforcedAdmin.userId = UserHandle.myUserId(); + } + CharSequence supportMessage = null; + if (UserHandle.isSameApp(Process.myUid(), Process.SYSTEM_UID)) { + supportMessage = dpm.getShortSupportMessageForUser( + enforcedAdmin.component, enforcedAdmin.userId); + } + if (supportMessage != null) { + final TextView textView = root.findViewById(R.id.admin_support_msg); + textView.setText(supportMessage); + } + } + } + + @VisibleForTesting + void showAdminPolicies(final EnforcedAdmin enforcedAdmin, final Activity activity) { + final Intent intent = new Intent(); + if (enforcedAdmin.component != null) { + intent.setClass(activity, DeviceAdminAdd.class); + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, + enforcedAdmin.component); + intent.putExtra(DeviceAdminAdd.EXTRA_CALLED_FROM_SUPPORT_DIALOG, true); + // DeviceAdminAdd class may need to run as managed profile. + activity.startActivityAsUser(intent, + new UserHandle(enforcedAdmin.userId)); + } else { + intent.setClass(activity, Settings.DeviceAdminSettingsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // Activity merges both managed profile and parent users + // admins so show as same user as this activity. + activity.startActivity(intent); + } + } +} diff --git a/src/com/android/settings/enterprise/OWNERS b/src/com/android/settings/enterprise/OWNERS index bab2fe2d1b9..58116739828 100644 --- a/src/com/android/settings/enterprise/OWNERS +++ b/src/com/android/settings/enterprise/OWNERS @@ -1,4 +1,7 @@ # Default reviewers for this and subdirectories. sandness@google.com +tonymak@google.com +yuemingw@google.com +arangelov@google.com # Emergency approvers in case the above are not available \ No newline at end of file diff --git a/src/com/android/settings/nfc/AndroidBeam.java b/src/com/android/settings/nfc/AndroidBeam.java index 8377f143f08..4e90680aac9 100644 --- a/src/com/android/settings/nfc/AndroidBeam.java +++ b/src/com/android/settings/nfc/AndroidBeam.java @@ -28,11 +28,11 @@ import android.view.ViewGroup; import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settingslib.HelpUtils; import com.android.settings.core.InstrumentedFragment; import com.android.settings.R; import com.android.settings.SettingsActivity; -import com.android.settings.ShowAdminSupportDetailsDialog; import com.android.settings.widget.SwitchBar; import com.android.settingslib.RestrictedLockUtils; @@ -70,11 +70,10 @@ public class AndroidBeam extends InstrumentedFragment mBeamDisallowedByBase = RestrictedLockUtils.hasBaseUserRestriction(getActivity(), UserManager.DISALLOW_OUTGOING_BEAM, UserHandle.myUserId()); if (!mBeamDisallowedByBase && admin != null) { - View view = inflater.inflate(R.layout.admin_support_details_empty_view, null); - ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), view, admin, false); - view.setVisibility(View.VISIBLE); + new ActionDisabledByAdminDialogHelper(getActivity()) + .prepareDialogBuilder(UserManager.DISALLOW_OUTGOING_BEAM, admin).show(); mBeamDisallowedByOnlyAdmin = true; - return view; + return new View(getContext()); } mView = inflater.inflate(R.layout.android_beam, container, false); return mView; diff --git a/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java b/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java new file mode 100644 index 00000000000..981ce135d7c --- /dev/null +++ b/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java @@ -0,0 +1,186 @@ +/* + * 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.enterprise; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.Process; +import android.os.UserManager; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.android.settings.DeviceAdminAdd; +import com.android.settings.R; +import com.android.settings.Settings; +import com.android.settings.testutils.CustomActivity; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowActivity; +import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; +import com.android.settings.testutils.shadow.ShadowProcess; +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = { + ShadowDevicePolicyManager.class, + ShadowUserManager.class, + ShadowActivity.class, + ShadowProcess.class +}) +public class ActionDisabledByAdminDialogHelperTest { + private ActionDisabledByAdminDialogHelper mHelper; + private Activity mActivity; + private org.robolectric.shadows.ShadowActivity mActivityShadow; + + @Before + public void setUp() { + mActivity = Robolectric.buildActivity(CustomActivity.class).get(); + mActivityShadow = Shadow.extract(mActivity); + mHelper = new ActionDisabledByAdminDialogHelper(mActivity); + } + + @Test + public void testShowAdminPoliciesWithComponent() { + final int userId = 123; + final ComponentName component = new ComponentName("some.package.name", + "some.package.name.SomeClass"); + final EnforcedAdmin admin = new EnforcedAdmin(component, userId); + + mHelper.showAdminPolicies(admin, mActivity); + + final Intent intent = mActivityShadow.getNextStartedActivity(); + assertTrue( + intent.getBooleanExtra(DeviceAdminAdd.EXTRA_CALLED_FROM_SUPPORT_DIALOG, false)); + assertEquals(component, + intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN)); + } + + @Test + public void testShowAdminPoliciesWithoutComponent() { + final int userId = 123; + final EnforcedAdmin admin = new EnforcedAdmin(null, userId); + mHelper.showAdminPolicies(admin, mActivity); + final Intent intent = mActivityShadow.getNextStartedActivity(); + assertEquals(intent.getComponent(), new ComponentName(mActivity, + Settings.DeviceAdminSettingsActivity.class.getName())); + } + + @Test + public void testSetAdminSupportTitle() { + final ViewGroup view = new FrameLayout(mActivity); + final TextView textView = new TextView(mActivity); + textView.setId(R.id.admin_support_dialog_title); + view.addView(textView); + + mHelper.setAdminSupportTitle(view, UserManager.DISALLOW_ADJUST_VOLUME); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title_adjust_volume)); + + mHelper.setAdminSupportTitle(view, UserManager.DISALLOW_OUTGOING_CALLS); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title_outgoing_calls)); + + mHelper.setAdminSupportTitle(view, UserManager.DISALLOW_SMS); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title_sms)); + + mHelper.setAdminSupportTitle(view, DevicePolicyManager.POLICY_DISABLE_CAMERA); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title_camera)); + + mHelper.setAdminSupportTitle(view, DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title_screen_capture)); + + mHelper.setAdminSupportTitle(view, DevicePolicyManager.POLICY_MANDATORY_BACKUPS); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title_turn_off_backups)); + + mHelper.setAdminSupportTitle(view, "another restriction"); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title)); + + mHelper.setAdminSupportTitle(view, null); + assertEquals(Shadows.shadowOf(textView).innerText(), + mActivity.getString(R.string.disabled_by_policy_title)); + } + + @Test + public void testSetAdminSupportDetails() { + final DevicePolicyManager dpm = RuntimeEnvironment.application.getSystemService( + DevicePolicyManager.class); + final ShadowDevicePolicyManager dpmShadow = Shadow.extract(dpm); + final UserManager userManager = RuntimeEnvironment.application.getSystemService( + UserManager.class); + final ShadowUserManager userManagerShadow = Shadow.extract(userManager); + final ViewGroup view = new FrameLayout(mActivity); + final ComponentName component = new ComponentName("some.package.name", + "some.package.name.SomeClass"); + final EnforcedAdmin admin = new EnforcedAdmin(component, 123); + final TextView textView = new TextView(mActivity); + + textView.setId(R.id.admin_support_msg); + view.addView(textView); + dpmShadow.setShortSupportMessageForUser(component, 123, "some message"); + dpmShadow.setIsAdminActiveAsUser(true); + userManagerShadow.addProfile(new UserInfo(123, null, 0)); + ShadowProcess.setMyUid(Process.SYSTEM_UID); + + mHelper.setAdminSupportDetails(mActivity, view, admin); + assertNotNull(admin.component); + assertEquals("some message", Shadows.shadowOf(textView).innerText()); + } + + @Test + public void testSetAdminSupportDetailsNotAdmin() { + final DevicePolicyManager dpm = RuntimeEnvironment.application.getSystemService( + DevicePolicyManager.class); + final ShadowDevicePolicyManager dpmShadow = Shadow.extract(dpm); + final UserManager userManager = RuntimeEnvironment.application.getSystemService( + UserManager.class); + final ShadowUserManager userManagerShadow = Shadow.extract(userManager); + final ComponentName component = new ComponentName("some.package.name", + "some.package.name.SomeClass"); + final EnforcedAdmin admin = new EnforcedAdmin(component, 123); + + dpmShadow.setShortSupportMessageForUser(component, 123, "some message"); + dpmShadow.setIsAdminActiveAsUser(false); + userManagerShadow.addProfile(new UserInfo(123, null, 0)); + + mHelper.setAdminSupportDetails(mActivity, null, admin); + assertNull(admin.component); + } +} + diff --git a/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogTest.java b/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogTest.java new file mode 100644 index 00000000000..ebdfad695fb --- /dev/null +++ b/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogTest.java @@ -0,0 +1,75 @@ +/* + * 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.enterprise; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Intent; +import android.os.UserHandle; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ActionDisabledByAdminDialogTest { + + private ActionDisabledByAdminDialog mDialog; + + @Before + public void setUp() { + mDialog = new ActionDisabledByAdminDialog(); + } + + @Test + public void testGetAdminDetailsFromIntent() { + final int userId = 123; + final ComponentName component = new ComponentName("com.some.package", ".SomeClass"); + final EnforcedAdmin expectedAdmin = new EnforcedAdmin(component, userId); + + final Intent intent = new Intent(); + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, component); + intent.putExtra(Intent.EXTRA_USER_ID, userId); + Assert.assertEquals(expectedAdmin, mDialog.getAdminDetailsFromIntent(intent)); + } + + @Test + public void testGetAdminDetailsFromNullIntent() { + final int userId = UserHandle.myUserId(); + final EnforcedAdmin expectedAdmin = new EnforcedAdmin(null, userId); + + Assert.assertEquals(expectedAdmin, mDialog.getAdminDetailsFromIntent(null)); + } + + @Test + public void testGetRestrictionFromIntent() { + final String restriction = "someRestriction"; + final Intent intent = new Intent(); + + intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, restriction); + Assert.assertEquals(restriction, mDialog.getRestrictionFromIntent(intent)); + } + + @Test + public void testGetRestrictionFromNullIntent() { + Assert.assertEquals(null, mDialog.getRestrictionFromIntent(null)); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/CustomActivity.java b/tests/robotests/src/com/android/settings/testutils/CustomActivity.java new file mode 100644 index 00000000000..d4c73412deb --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/CustomActivity.java @@ -0,0 +1,26 @@ +/* + * 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.testutils; + +import android.app.Activity; +import android.content.Intent; +import android.os.UserHandle; + +public class CustomActivity extends Activity { + @Override + public void startActivityAsUser(Intent intent, UserHandle user) {} +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivity.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivity.java new file mode 100644 index 00000000000..0f67bbf045e --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowActivity.java @@ -0,0 +1,35 @@ +/* + * 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.testutils.shadow; + +import android.content.Intent; +import android.os.UserHandle; + +import com.android.settings.testutils.CustomActivity; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowApplication; + +@Implements(CustomActivity.class) +public class ShadowActivity extends org.robolectric.shadows.ShadowActivity { + + @Implementation + public void startActivityAsUser(Intent intent, UserHandle user) { + ShadowApplication.getInstance().startActivity(intent); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java new file mode 100644 index 00000000000..77daae00c5a --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDevicePolicyManager.java @@ -0,0 +1,41 @@ +package com.android.settings.testutils.shadow; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This shadow if using {@link ShadowDevicePolicyManagerWrapper} is not possible. + */ +@Implements(DevicePolicyManager.class) +public class ShadowDevicePolicyManager extends org.robolectric.shadows.ShadowDevicePolicyManager { + private Map mSupportMessagesMap = new HashMap<>(); + private boolean mIsAdminActiveAsUser = false; + + public void setShortSupportMessageForUser(ComponentName admin, int userHandle, String message) { + mSupportMessagesMap.put(Objects.hash(admin, userHandle), message); + } + + @Implementation + public @Nullable CharSequence getShortSupportMessageForUser(@NonNull ComponentName admin, + int userHandle) { + return mSupportMessagesMap.get(Objects.hash(admin, userHandle)); + } + + @Implementation + public boolean isAdminActiveAsUser(@NonNull ComponentName admin, int userId) { + return mIsAdminActiveAsUser; + } + + public void setIsAdminActiveAsUser(boolean active) { + mIsAdminActiveAsUser = active; + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowProcess.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowProcess.java new file mode 100644 index 00000000000..eea3ee896e7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowProcess.java @@ -0,0 +1,18 @@ +package com.android.settings.testutils.shadow; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(android.os.Process.class) +public class ShadowProcess { + private static int sUid; + + public static void setMyUid(int uid) { + sUid = uid; + } + + @Implementation + public static int myUid() { + return sUid; + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java index dfd4b7f3bc6..9979ddbb2f6 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java @@ -17,7 +17,6 @@ package com.android.settings.testutils.shadow; import android.annotation.UserIdInt; -import android.content.Context; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; @@ -31,7 +30,6 @@ import org.robolectric.annotation.Resetter; import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,10 +40,12 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager private SparseArray mUserInfos = new SparseArray<>(); private final List mRestrictions = new ArrayList<>(); private final Map> mRestrictionSources = new HashMap<>(); + private List mUserProfileInfos = new ArrayList<>(); @Resetter public void reset() { mRestrictions.clear(); + mUserProfileInfos.clear(); } public void setUserInfo(int userHandle, UserInfo userInfo) { @@ -57,9 +57,13 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager return mUserInfos.get(userHandle); } + public void addProfile(UserInfo userInfo) { + mUserProfileInfos.add(userInfo); + } + @Implementation public List getProfiles(@UserIdInt int userHandle) { - return Collections.emptyList(); + return mUserProfileInfos; } @Implementation From 2b6876ccabe3d8194edd0bba540aa5607b406804 Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Wed, 28 Feb 2018 19:31:31 +0000 Subject: [PATCH 03/17] Time zone, Region, UTC picker - Extract most common view related codes into BaseTimeZoneAdapter and BaseTimeZonePicker. Subclass handles the text formatting and order. - Search view is added compared to previous version of time zone picker - SpannableUtil is added to preserve spannable when formatting String resource. - Fix the bug using GMT+ as time zone id. b/73132985 - Fix Talkback treating flags on screens as a separate element Bug: 72146259 Bug: 73132985 Bug: 73952488 Test: mm RunSettingsRoboTests Change-Id: I42c6ac369199c09d11e7f5cc4707358fa4780fed (cherry picked from commit fbd30acef09d1db6aaf767c2bcbb17787fd08ba1) --- res/layout/time_zone_search_item.xml | 99 ++++++++ res/menu/time_zone_base_search_menu.xml | 26 +++ res/values/strings.xml | 18 +- .../timezone/BaseTimeZoneAdapter.java | 206 +++++++++++++++++ .../timezone/BaseTimeZoneInfoPicker.java | 166 ++++++++++++++ .../datetime/timezone/BaseTimeZonePicker.java | 159 +++++++++++++ .../datetime/timezone/FixedOffsetPicker.java | 69 ++++++ .../datetime/timezone/RegionSearchPicker.java | 211 ++++++++++++++++++ .../datetime/timezone/RegionZonePicker.java | 133 +++++++++++ .../datetime/timezone/SpannableUtil.java | 41 ++++ .../datetime/timezone/model/TimeZoneData.java | 2 +- .../timezone/BaseTimeZoneAdapterTest.java | 141 ++++++++++++ .../timezone/BaseTimeZoneInfoPickerTest.java | 123 ++++++++++ .../timezone/FixedOffsetPickerTest.java | 56 +++++ .../timezone/RegionSearchPickerTest.java | 59 +++++ .../timezone/RegionZonePickerTest.java | 57 +++++ .../datetime/timezone/SpannableUtilTest.java | 40 ++++ 17 files changed, 1603 insertions(+), 3 deletions(-) create mode 100644 res/layout/time_zone_search_item.xml create mode 100644 res/menu/time_zone_base_search_menu.xml create mode 100644 src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java create mode 100644 src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java create mode 100644 src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java create mode 100644 src/com/android/settings/datetime/timezone/FixedOffsetPicker.java create mode 100644 src/com/android/settings/datetime/timezone/RegionSearchPicker.java create mode 100644 src/com/android/settings/datetime/timezone/RegionZonePicker.java create mode 100644 src/com/android/settings/datetime/timezone/SpannableUtil.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePickerTest.java create mode 100644 tests/robotests/src/com/android/settings/datetime/timezone/SpannableUtilTest.java diff --git a/res/layout/time_zone_search_item.xml b/res/layout/time_zone_search_item.xml new file mode 100644 index 00000000000..bb75226714d --- /dev/null +++ b/res/layout/time_zone_search_item.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/time_zone_base_search_menu.xml b/res/menu/time_zone_base_search_menu.xml new file mode 100644 index 00000000000..92241af4551 --- /dev/null +++ b/res/menu/time_zone_base_search_menu.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 4e81b7a129c..3452af0e970 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -748,20 +748,34 @@ Date Set date + + Region + + Time Zone + + Select UTC offset Sort alphabetically Sort by time zone %1$s starts on %2$s. + + %1$s (%2$s) + + %2$s (%1$s) + + Uses %1$s. %2$s starts on %3$s. + + Uses %1$s. No daylight savings time. Daylight savings time Standard time - Time zone by region + Show time zones by region - Fixed offset time zones + Show time zones by UTC offset diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java new file mode 100644 index 00000000000..effa9485a2f --- /dev/null +++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java @@ -0,0 +1,206 @@ +/* + * 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.datetime.timezone; + +import android.icu.text.BreakIterator; +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Filter; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.datetime.timezone.BaseTimeZonePicker.OnListItemClickListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Used with {@class BaseTimeZonePicker}. It renders text in each item into list view. A list of + * {@class AdapterItem} must be provided when an instance is created. + */ +public class BaseTimeZoneAdapter + extends RecyclerView.Adapter { + + private final List mOriginalItems; + private final OnListItemClickListener mOnListItemClickListener; + private final Locale mLocale; + private final boolean mShowItemSummary; + + private List mItems; + private ArrayFilter mFilter; + + public BaseTimeZoneAdapter(List items, OnListItemClickListener + onListItemClickListener, Locale locale, boolean showItemSummary) { + mOriginalItems = items; + mItems = items; + mOnListItemClickListener = onListItemClickListener; + mLocale = locale; + mShowItemSummary = showItemSummary; + setHasStableIds(true); + } + + @NonNull + @Override + public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.time_zone_search_item, parent, false); + return new ItemViewHolder(view, mOnListItemClickListener); + } + + @Override + public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { + final AdapterItem item = mItems.get(position); + holder.mSummaryFrame.setVisibility( + mShowItemSummary ? View.VISIBLE : View.GONE); + holder.mTitleView.setText(item.getTitle()); + holder.mIconTextView.setText(item.getIconText()); + holder.mSummaryView.setText(item.getSummary()); + holder.mTimeView.setText(item.getCurrentTime()); + holder.setPosition(position); + } + + @Override + public long getItemId(int position) { + return getItem(position).getItemId(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + public @NonNull + Filter getFilter() { + if (mFilter == null) { + mFilter = new ArrayFilter(); + } + return mFilter; + } + + public T getItem(int position) { + return mItems.get(position); + } + + public interface AdapterItem { + CharSequence getTitle(); + CharSequence getSummary(); + String getIconText(); + String getCurrentTime(); + long getItemId(); + String[] getSearchKeys(); + } + + public static class ItemViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener{ + + final OnListItemClickListener mOnListItemClickListener; + final View mSummaryFrame; + final TextView mTitleView; + final TextView mIconTextView; + final TextView mSummaryView; + final TextView mTimeView; + private int mPosition; + + public ItemViewHolder(View itemView, OnListItemClickListener onListItemClickListener) { + super(itemView); + itemView.setOnClickListener(this); + mSummaryFrame = itemView.findViewById(R.id.summary_frame); + mTitleView = itemView.findViewById(android.R.id.title); + mIconTextView = itemView.findViewById(R.id.icon_text); + mSummaryView = itemView.findViewById(android.R.id.summary); + mTimeView = itemView.findViewById(R.id.current_time); + mOnListItemClickListener = onListItemClickListener; + } + + public void setPosition(int position) { + mPosition = position; + } + + @Override + public void onClick(View v) { + mOnListItemClickListener.onListItemClick(mPosition); + } + } + + /** + *

An array filter constrains the content of the array adapter with + * a prefix. Each item that does not start with the supplied prefix + * is removed from the list.

+ * + * The filtering operation is not optimized, due to small data size (~260 regions), + * require additional pre-processing. Potentially, a trie structure can be used to match + * prefixes of the search keys. + */ + private class ArrayFilter extends Filter { + + private BreakIterator mBreakIterator = BreakIterator.getWordInstance(mLocale); + + @WorkerThread + @Override + protected FilterResults performFiltering(CharSequence prefix) { + final List newItems; + if (TextUtils.isEmpty(prefix)) { + newItems = mOriginalItems; + } else { + final String prefixString = prefix.toString().toLowerCase(mLocale); + newItems = new ArrayList<>(); + + for (T item : mOriginalItems) { + outer: + for (String searchKey : item.getSearchKeys()) { + searchKey = searchKey.toLowerCase(mLocale); + // First match against the whole, non-splitted value + if (searchKey.startsWith(prefixString)) { + newItems.add(item); + break outer; + } else { + mBreakIterator.setText(searchKey); + for (int wordStart = 0, wordLimit = mBreakIterator.next(); + wordLimit != BreakIterator.DONE; + wordStart = wordLimit, + wordLimit = mBreakIterator.next()) { + if (mBreakIterator.getRuleStatus() != BreakIterator.WORD_NONE + && searchKey.startsWith(prefixString, wordStart)) { + newItems.add(item); + break outer; + } + } + } + } + } + } + + final FilterResults results = new FilterResults(); + results.values = newItems; + results.count = newItems.size(); + + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mItems = (List) results.values; + notifyDataSetChanged(); + } + } +} diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java new file mode 100644 index 00000000000..b1335823af5 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java @@ -0,0 +1,166 @@ +/* + * 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.datetime.timezone; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.icu.text.DateFormat; +import android.icu.text.SimpleDateFormat; +import android.icu.util.Calendar; + +import com.android.settings.R; +import com.android.settings.datetime.timezone.model.TimeZoneData; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +/** + * Render a list of {@class TimeZoneInfo} into the list view in {@class BaseTimeZonePicker} + */ +public abstract class BaseTimeZoneInfoPicker extends BaseTimeZonePicker { + protected static final String TAG = "RegionZoneSearchPicker"; + protected ZoneAdapter mAdapter; + + protected BaseTimeZoneInfoPicker(int titleResId, int searchHintResId, + boolean searchEnabled, boolean defaultExpandSearch) { + super(titleResId, searchHintResId, searchEnabled, defaultExpandSearch); + } + + @Override + protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) { + mAdapter = new ZoneAdapter(getContext(), getAllTimeZoneInfos(timeZoneData), + this::onListItemClick, getLocale()); + return mAdapter; + } + + private void onListItemClick(int position) { + final TimeZoneInfo timeZoneInfo = mAdapter.getItem(position).mTimeZoneInfo; + getActivity().setResult(Activity.RESULT_OK, prepareResultData(timeZoneInfo)); + getActivity().finish(); + } + + protected Intent prepareResultData(TimeZoneInfo selectedTimeZoneInfo) { + return new Intent().putExtra(EXTRA_RESULT_TIME_ZONE_ID, selectedTimeZoneInfo.getId()); + } + + public abstract List getAllTimeZoneInfos(TimeZoneData timeZoneData); + + protected static class ZoneAdapter extends BaseTimeZoneAdapter { + + public ZoneAdapter(Context context, List timeZones, + OnListItemClickListener onListItemClickListener, Locale locale) { + super(createTimeZoneInfoItems(context, timeZones, locale), + onListItemClickListener, locale, true /* showItemSummary */); + } + + private static List createTimeZoneInfoItems(Context context, + List timeZones, Locale locale) { + final DateFormat currentTimeFormat = new SimpleDateFormat( + android.text.format.DateFormat.getTimeFormatString(context), locale); + final ArrayList results = new ArrayList<>(timeZones.size()); + final Resources resources = context.getResources(); + long i = 0; + for (TimeZoneInfo timeZone : timeZones) { + results.add(new TimeZoneInfoItem(i++, timeZone, resources, currentTimeFormat)); + } + return results; + } + } + + private static class TimeZoneInfoItem implements BaseTimeZoneAdapter.AdapterItem { + private final long mItemId; + private final TimeZoneInfo mTimeZoneInfo; + private final Resources mResources; + private final DateFormat mTimeFormat; + private final String mTitle; + private final String[] mSearchKeys; + + private TimeZoneInfoItem(long itemId, TimeZoneInfo timeZoneInfo, Resources resources, + DateFormat timeFormat) { + mItemId = itemId; + mTimeZoneInfo = timeZoneInfo; + mResources = resources; + mTimeFormat = timeFormat; + mTitle = createTitle(timeZoneInfo); + mSearchKeys = new String[] { mTitle }; + } + + private static String createTitle(TimeZoneInfo timeZoneInfo) { + String name = timeZoneInfo.getExemplarLocation(); + if (name == null) { + name = timeZoneInfo.getGenericName(); + } + if (name == null && timeZoneInfo.getTimeZone().inDaylightTime(new Date())) { + name = timeZoneInfo.getDaylightName(); + } + if (name == null) { + name = timeZoneInfo.getStandardName(); + } + if (name == null) { + name = String.valueOf(timeZoneInfo.getGmtOffset()); + } + return name; + } + + @Override + public CharSequence getTitle() { + return mTitle; + } + + @Override + public CharSequence getSummary() { + String name = mTimeZoneInfo.getGenericName(); + if (name == null) { + if (mTimeZoneInfo.getTimeZone().inDaylightTime(new Date())) { + name = mTimeZoneInfo.getDaylightName(); + } else { + name = mTimeZoneInfo.getStandardName(); + } + } + if (name == null) { + return mTimeZoneInfo.getGmtOffset(); + } else { + return SpannableUtil.getResourcesText(mResources, + R.string.zone_info_offset_and_name, mTimeZoneInfo.getGmtOffset(), name); + } + } + + @Override + public String getIconText() { + return null; + } + + @Override + public String getCurrentTime() { + return mTimeFormat.format(Calendar.getInstance(mTimeZoneInfo.getTimeZone())); + } + + @Override + public long getItemId() { + return mItemId; + } + + @Override + public String[] getSearchKeys() { + return mSearchKeys; + } + } +} diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java new file mode 100644 index 00000000000..c5e1ccb431a --- /dev/null +++ b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java @@ -0,0 +1,159 @@ +/* + * 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.datetime.timezone; + +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SearchView; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.core.InstrumentedFragment; +import com.android.settings.datetime.timezone.model.TimeZoneData; +import com.android.settings.datetime.timezone.model.TimeZoneDataLoader; + +import java.util.Locale; + +/** + * It's abstract class. Subclass should use it with {@class BaseTimeZoneAdapter} and + * {@class AdapterItem} to provide a list view with text search capability. + * The search matches the prefix of words in the search text. + */ +public abstract class BaseTimeZonePicker extends InstrumentedFragment + implements SearchView.OnQueryTextListener{ + + public static final String EXTRA_RESULT_REGION_ID = + "com.android.settings.datetime.timezone.result_region_id"; + public static final String EXTRA_RESULT_TIME_ZONE_ID = + "com.android.settings.datetime.timezone.result_time_zone_id"; + private final int mTitleResId; + private final int mSearchHintResId; + private final boolean mSearchEnabled; + private final boolean mDefaultExpandSearch; + + protected Locale mLocale; + private BaseTimeZoneAdapter mAdapter; + private RecyclerView mRecyclerView; + private TimeZoneData mTimeZoneData; + + private SearchView mSearchView; + + /** + * Constructor called by subclass. + * @param defaultExpandSearch whether expand the search view when first launching the fragment + */ + protected BaseTimeZonePicker(int titleResId, int searchHintResId, + boolean searchEnabled, boolean defaultExpandSearch) { + mTitleResId = titleResId; + mSearchHintResId = searchHintResId; + mSearchEnabled = searchEnabled; + mDefaultExpandSearch = defaultExpandSearch; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + getActivity().setTitle(mTitleResId); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.recycler_view, container, false); + mRecyclerView = view.findViewById(R.id.recycler_view); + mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), + LinearLayoutManager.VERTICAL, /* reverseLayout */ false)); + mRecyclerView.setAdapter(mAdapter); + + // Initialize TimeZoneDataLoader only when mRecyclerView is ready to avoid race + // during onDateLoaderReady callback. + getLoaderManager().initLoader(0, null, new TimeZoneDataLoader.LoaderCreator( + getContext(), this::onTimeZoneDataReady)); + return view; + } + + public void onTimeZoneDataReady(TimeZoneData timeZoneData) { + if (mTimeZoneData == null && timeZoneData != null) { + mTimeZoneData = timeZoneData; + mAdapter = createAdapter(mTimeZoneData); + if (mRecyclerView != null) { + mRecyclerView.setAdapter(mAdapter); + } + } + } + + protected Locale getLocale() { + return getContext().getResources().getConfiguration().getLocales().get(0); + } + + /** + * Called when TimeZoneData is ready. + */ + protected abstract BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData); + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (mSearchEnabled) { + inflater.inflate(R.menu.time_zone_base_search_menu, menu); + + final MenuItem searchMenuItem = menu.findItem(R.id.time_zone_search_menu); + mSearchView = (SearchView) searchMenuItem.getActionView(); + + mSearchView.setQueryHint(getText(mSearchHintResId)); + mSearchView.setOnQueryTextListener(this); + + if (mDefaultExpandSearch) { + searchMenuItem.expandActionView(); + mSearchView.setIconified(false); + mSearchView.setActivated(true); + mSearchView.setQuery("", true /* submit */); + } + } + } + + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + if (mAdapter != null) { + mAdapter.getFilter().filter(newText); + } + return false; + } + + @Override + public int getMetricsCategory() { + // TODO: use a new metrics id? + return MetricsEvent.ZONE_PICKER; + } + + public interface OnListItemClickListener { + void onListItemClick(int position); + } + +} diff --git a/src/com/android/settings/datetime/timezone/FixedOffsetPicker.java b/src/com/android/settings/datetime/timezone/FixedOffsetPicker.java new file mode 100644 index 00000000000..3d8b826a73d --- /dev/null +++ b/src/com/android/settings/datetime/timezone/FixedOffsetPicker.java @@ -0,0 +1,69 @@ +/* + * 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.datetime.timezone; + +import android.icu.util.TimeZone; + +import com.android.settings.R; +import com.android.settings.datetime.timezone.model.TimeZoneData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +/** + * Render a list of fixed offset time zone {@class TimeZoneInfo} into a list view. + */ +public class FixedOffsetPicker extends BaseTimeZoneInfoPicker { + /** + * Range of integer fixed UTC offsets shown in the pickers. + */ + private static final int MIN_HOURS_OFFSET = -14; + private static final int MAX_HOURS_OFFSET = +12; + + public FixedOffsetPicker() { + super(R.string.date_time_select_fixed_offset_time_zones, + R.string.search_settings, false, false); + } + + @Override + public List getAllTimeZoneInfos(TimeZoneData timeZoneData) { + return loadFixedOffsets(); + } + + /** + * Returns a {@link TimeZoneInfo} for each fixed offset time zone, such as UTC or GMT+4. The + * returned list will be sorted in a reasonable way for display. + */ + private List loadFixedOffsets() { + final TimeZoneInfo.Formatter formatter = new TimeZoneInfo.Formatter(getLocale(), + new Date()); + final List timeZoneInfos = new ArrayList<>(); + timeZoneInfos.add(formatter.format(TimeZone.getFrozenTimeZone("Etc/UTC"))); + for (int hoursOffset = MAX_HOURS_OFFSET; hoursOffset >= MIN_HOURS_OFFSET; --hoursOffset) { + if (hoursOffset == 0) { + // UTC is handled above, so don't add GMT +/-0 again. + continue; + } + final String id = String.format(Locale.US, "Etc/GMT%+d", hoursOffset); + timeZoneInfos.add(formatter.format(TimeZone.getFrozenTimeZone(id))); + } + return Collections.unmodifiableList(timeZoneInfos); + } +} diff --git a/src/com/android/settings/datetime/timezone/RegionSearchPicker.java b/src/com/android/settings/datetime/timezone/RegionSearchPicker.java new file mode 100644 index 00000000000..1381b209feb --- /dev/null +++ b/src/com/android/settings/datetime/timezone/RegionSearchPicker.java @@ -0,0 +1,211 @@ +/* + * 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.datetime.timezone; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Paint; +import android.icu.text.Collator; +import android.icu.text.LocaleDisplayNames; +import android.os.Bundle; +import android.util.Log; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones; +import com.android.settings.datetime.timezone.model.TimeZoneData; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * Render a list of regions into a list view. + */ +public class RegionSearchPicker extends BaseTimeZonePicker { + private static final int REQUEST_CODE_ZONE_PICKER = 1; + private static final String TAG = "RegionSearchPicker"; + + private BaseTimeZoneAdapter mAdapter; + private TimeZoneData mTimeZoneData; + + public RegionSearchPicker() { + super(R.string.date_time_select_region, R.string.search_settings, true, true); + } + + @Override + protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) { + mTimeZoneData = timeZoneData; + mAdapter = new BaseTimeZoneAdapter<>(createAdapterItem(timeZoneData.getRegionIds()), + this::onListItemClick, getLocale(), false); + return mAdapter; + } + + private void onListItemClick(int position) { + final String regionId = mAdapter.getItem(position).getId(); + final FilteredCountryTimeZones countryTimeZones = mTimeZoneData.lookupCountryTimeZones( + regionId); + final Activity activity = getActivity(); + if (countryTimeZones == null || countryTimeZones.getTimeZoneIds().isEmpty()) { + Log.e(TAG, "Region has no time zones: " + regionId); + activity.setResult(Activity.RESULT_CANCELED); + activity.finish(); + return; + } + + List timeZoneIds = countryTimeZones.getTimeZoneIds(); + // Choose the time zone associated the region if there is only one time zone in that region + if (timeZoneIds.size() == 1) { + final Intent resultData = new Intent() + .putExtra(EXTRA_RESULT_REGION_ID, regionId) + .putExtra(EXTRA_RESULT_TIME_ZONE_ID, timeZoneIds.get(0)); + getActivity().setResult(Activity.RESULT_OK, resultData); + getActivity().finish(); + } else { + // Launch the zone picker and let the user choose a time zone from the list of + // time zones associated with the region. + final Bundle args = new Bundle(); + args.putString(RegionZonePicker.EXTRA_REGION_ID, regionId); + new SubSettingLauncher(getContext()) + .setDestination(RegionZonePicker.class.getCanonicalName()) + .setArguments(args) + .setSourceMetricsCategory(getMetricsCategory()) + .setResultListener(this, REQUEST_CODE_ZONE_PICKER) + .launch(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_ZONE_PICKER) { + if (resultCode == Activity.RESULT_OK) { + getActivity().setResult(Activity.RESULT_OK, data); + } + getActivity().finish(); + } + } + + private List createAdapterItem(Set regionIds) { + final Collator collator = Collator.getInstance(getLocale()); + final TreeSet items = new TreeSet<>(new RegionInfoComparator(collator)); + final Paint paint = new Paint(); + final LocaleDisplayNames localeDisplayNames = LocaleDisplayNames.getInstance(getLocale()); + long i = 0; + for (String regionId : regionIds) { + String name = localeDisplayNames.regionDisplayName(regionId); + String regionalIndicator = createRegionalIndicator(regionId, paint); + items.add(new RegionItem(i++, regionId, name, regionalIndicator)); + } + return new ArrayList<>(items); + } + + /** + * Create a Unicode Region Indicator Symbol for a given region id (a.k.a flag emoji). If the + * system can't render a flag for this region or the input is not a region id, this returns + * {@code null}. + * + * @param id the two-character region id. + * @param paint Paint contains the glyph + * @return a String representing the flag of the region or {@code null}. + */ + private static String createRegionalIndicator(String id, Paint paint) { + if (id.length() != 2) { + return null; + } + final char c1 = id.charAt(0); + final char c2 = id.charAt(1); + if ('A' > c1 || c1 > 'Z' || 'A' > c2 || c2 > 'Z') { + return null; + } + // Regional Indicator A is U+1F1E6 which is 0xD83C 0xDDE6 in UTF-16. + final String regionalIndicator = new String( + new char[]{0xd83c, (char) (0xdde6 - 'A' + c1), 0xd83c, (char) (0xdde6 - 'A' + c2)}); + if (!paint.hasGlyph(regionalIndicator)) { + return null; + } + return regionalIndicator; + } + + private static class RegionItem implements BaseTimeZoneAdapter.AdapterItem { + + private final String mId; + private final String mName; + private final String mRegionalIndicator; + private final long mItemId; + private final String[] mSearchKeys; + + RegionItem(long itemId, String id, String name, String regionalIndicator) { + mId = id; + mName = name; + mRegionalIndicator = regionalIndicator; + mItemId = itemId; + // Allow to search with ISO_3166-1 alpha-2 code. It's handy for english users in some + // countries, e.g. US for United States. It's not best search keys for users, but + // ICU doesn't have the data for the alias names of a region. + mSearchKeys = new String[] {mId, mName}; + } + + public String getId() { + return mId; + } + + @Override + public CharSequence getTitle() { + return mName; + } + + @Override + public CharSequence getSummary() { + return null; + } + + @Override + public String getIconText() { + return mRegionalIndicator; + } + + @Override + public String getCurrentTime() { + return null; + } + + @Override + public long getItemId() { + return mItemId; + } + + @Override + public String[] getSearchKeys() { + return mSearchKeys; + } + } + + private static class RegionInfoComparator implements Comparator { + private final Collator mCollator; + + RegionInfoComparator(Collator collator) { + mCollator = collator; + } + + @Override + public int compare(RegionItem r1, RegionItem r2) { + return mCollator.compare(r1.getTitle(), r2.getTitle()); + } + } +} diff --git a/src/com/android/settings/datetime/timezone/RegionZonePicker.java b/src/com/android/settings/datetime/timezone/RegionZonePicker.java new file mode 100644 index 00000000000..7805241aec8 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/RegionZonePicker.java @@ -0,0 +1,133 @@ +/* + * 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.datetime.timezone; + +import android.content.Intent; +import android.icu.text.Collator; +import android.icu.util.TimeZone; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.settings.R; +import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones; +import com.android.settings.datetime.timezone.model.TimeZoneData; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.TreeSet; + +/** + * Given a region, render a list of time zone {@class TimeZoneInfo} into a list view. + */ +public class RegionZonePicker extends BaseTimeZoneInfoPicker { + + public static final String EXTRA_REGION_ID = + "com.android.settings.datetime.timezone.region_id"; + + public RegionZonePicker() { + super(R.string.date_time_select_zone, R.string.search_settings, true, false); + } + + /** + * Add the extra region id into the result. + */ + @Override + protected Intent prepareResultData(TimeZoneInfo selectedTimeZoneInfo) { + final Intent intent = super.prepareResultData(selectedTimeZoneInfo); + intent.putExtra(EXTRA_RESULT_REGION_ID, getArguments().getString(EXTRA_REGION_ID)); + return intent; + } + + @Override + public List getAllTimeZoneInfos(TimeZoneData timeZoneData) { + if (getArguments() == null) { + Log.e(TAG, "getArguments() == null"); + getActivity().finish(); + } + String regionId = getArguments().getString(EXTRA_REGION_ID); + + FilteredCountryTimeZones filteredCountryTimeZones = timeZoneData.lookupCountryTimeZones( + regionId); + if (filteredCountryTimeZones == null) { + Log.e(TAG, "region id is not valid: " + regionId); + getActivity().finish(); + } + + // It could be a timely operations if there are many time zones. A region in time zone data + // contains a maximum of 29 time zones currently. It may change in the future, but it's + // unlikely to be changed drastically. + return getRegionTimeZoneInfo(filteredCountryTimeZones.getTimeZoneIds()); + } + + /** + * Returns a list of {@link TimeZoneInfo} objects. The returned list will be sorted properly for + * display in the locale.It may be smaller than the input collection, if equivalent IDs are + * passed in. + * + * @param timeZoneIds a list of Olson IDs. + */ + public List getRegionTimeZoneInfo(Collection timeZoneIds) { + final TimeZoneInfo.Formatter formatter = new TimeZoneInfo.Formatter(getLocale(), + new Date()); + final TreeSet timeZoneInfos = + new TreeSet<>(new TimeZoneInfoComparator(Collator.getInstance(getLocale()), + new Date())); + + for (final String timeZoneId : timeZoneIds) { + final TimeZone timeZone = TimeZone.getFrozenTimeZone(timeZoneId); + // Skip time zone ICU isn't aware. + if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) { + continue; + } + timeZoneInfos.add(formatter.format(timeZone)); + } + return Collections.unmodifiableList(new ArrayList<>(timeZoneInfos)); + } + + @VisibleForTesting + static class TimeZoneInfoComparator implements Comparator { + private Collator mCollator; + private final Date mNow; + + @VisibleForTesting + TimeZoneInfoComparator(Collator collator, Date now) { + mCollator = collator; + mNow = now; + } + + @Override + public int compare(TimeZoneInfo tzi1, TimeZoneInfo tzi2) { + int result = Integer.compare(tzi1.getTimeZone().getOffset(mNow.getTime()), + tzi2.getTimeZone().getOffset(mNow.getTime())); + if (result == 0) { + result = Integer.compare(tzi1.getTimeZone().getRawOffset(), + tzi2.getTimeZone().getRawOffset()); + } + if (result == 0) { + result = mCollator.compare(tzi1.getExemplarLocation(), tzi2.getExemplarLocation()); + } + if (result == 0 && tzi1.getGenericName() != null && tzi2.getGenericName() != null) { + result = mCollator.compare(tzi1.getGenericName(), tzi2.getGenericName()); + } + return result; + } + } +} diff --git a/src/com/android/settings/datetime/timezone/SpannableUtil.java b/src/com/android/settings/datetime/timezone/SpannableUtil.java new file mode 100644 index 00000000000..49c3e7d55e0 --- /dev/null +++ b/src/com/android/settings/datetime/timezone/SpannableUtil.java @@ -0,0 +1,41 @@ +/* + * 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.datetime.timezone; + +import android.annotation.StringRes; +import android.content.res.Resources; +import android.text.Spannable; +import android.text.SpannableStringBuilder; + +import java.util.Formatter; +import java.util.Locale; + + +public class SpannableUtil { + + /** + * {@class Resources} has no method to format string resource with {@class Spannable} a + * rguments. It's a helper method for this purpose. + */ + public static Spannable getResourcesText(Resources res, @StringRes int resId, + Object... args) { + final Locale locale = res.getConfiguration().getLocales().get(0); + final SpannableStringBuilder builder = new SpannableStringBuilder(); + new Formatter(builder, locale).format(res.getString(resId), args); + return builder; + } +} diff --git a/src/com/android/settings/datetime/timezone/model/TimeZoneData.java b/src/com/android/settings/datetime/timezone/model/TimeZoneData.java index a863bfc7ab2..b78534d6671 100644 --- a/src/com/android/settings/datetime/timezone/model/TimeZoneData.java +++ b/src/com/android/settings/datetime/timezone/model/TimeZoneData.java @@ -57,7 +57,7 @@ public class TimeZoneData { } @VisibleForTesting - TimeZoneData(CountryZonesFinder countryZonesFinder) { + public TimeZoneData(CountryZonesFinder countryZonesFinder) { mCountryZonesFinder = countryZonesFinder; mRegionIds = getNormalizedRegionIds(mCountryZonesFinder.lookupAllCountryIsoCodes()); } diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java new file mode 100644 index 00000000000..c85c598491a --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java @@ -0,0 +1,141 @@ +/* + * 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.datetime.timezone; + +import android.support.v7.widget.RecyclerView.AdapterDataObserver; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +public class BaseTimeZoneAdapterTest { + + @Test + public void testFilter() throws InterruptedException { + TestItem US = new TestItem("United States"); + TestItem HK = new TestItem("Hong Kong"); + TestItem UK = new TestItem("United Kingdom", new String[] { "United Kingdom", + "Great Britain"}); + TestItem secretCountry = new TestItem("no name", new String[] { "Secret"}); + List items = new ArrayList<>(); + items.add(US); + items.add(HK); + items.add(UK); + items.add(secretCountry); + + TestTimeZoneAdapter adapter = new TestTimeZoneAdapter(items); + assertSearch(adapter, "", items.toArray(new TestItem[items.size()])); + assertSearch(adapter, "Unit", US, UK); + assertSearch(adapter, "kon", HK); + assertSearch(adapter, "brit", UK); + assertSearch(adapter, "sec", secretCountry); + } + + private void assertSearch(TestTimeZoneAdapter adapter , String searchText, TestItem... items) + throws InterruptedException { + Observer observer = new Observer(adapter); + adapter.getFilter().filter(searchText); + observer.await(); + assertThat(adapter.getItemCount()).isEqualTo(items.length); + for (int i = 0; i < items.length; i++) { + assertThat(adapter.getItem(i)).isEqualTo(items[i]); + } + } + + private static class Observer extends AdapterDataObserver { + + private final CountDownLatch mLatch = new CountDownLatch(1); + private final TestTimeZoneAdapter mAdapter; + + public Observer(TestTimeZoneAdapter adapter) { + mAdapter = adapter; + mAdapter.registerAdapterDataObserver(this); + } + + @Override + public void onChanged() { + mAdapter.unregisterAdapterDataObserver(this); + mLatch.countDown(); + } + + public void await() throws InterruptedException { + mLatch.await(2L, TimeUnit.SECONDS); + } + } + + private static class TestTimeZoneAdapter extends BaseTimeZoneAdapter { + + public TestTimeZoneAdapter(List items) { + super(items, position -> {}, Locale.US, false); + } + } + + private static class TestItem implements BaseTimeZoneAdapter.AdapterItem { + + private final String mTitle; + private final String[] mSearchKeys; + + TestItem(String title) { + this(title, new String[] { title }); + } + + TestItem(String title, String[] searchKeys) { + mTitle = title; + mSearchKeys = searchKeys; + } + + @Override + public CharSequence getTitle() { + return mTitle; + } + + @Override + public CharSequence getSummary() { + return null; + } + + @Override + public String getIconText() { + return null; + } + + @Override + public String getCurrentTime() { + return null; + } + + @Override + public long getItemId() { + return 0; + } + + @Override + public String[] getSearchKeys() { + return mSearchKeys; + } + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java new file mode 100644 index 00000000000..0d47a3a9144 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java @@ -0,0 +1,123 @@ +/* + * 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.datetime.timezone; + +import android.content.Context; +import android.icu.util.TimeZone; + +import com.android.settings.datetime.timezone.model.TimeZoneData; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import com.google.common.truth.Truth; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import static org.mockito.Mockito.mock; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = { BaseTimeZoneInfoPickerTest.ShadowDataFormat.class }) +public class BaseTimeZoneInfoPickerTest { + @Implements(android.text.format.DateFormat.class) + public static class ShadowDataFormat { + + public static String sTimeFormatString = ""; + + @Implementation + public static String getTimeFormatString(Context context) { + return sTimeFormatString; + } + } + + /** + * Verify the summary, title, and time label in a time zone item are formatted properly. + */ + @Test + public void createAdapter_matchTimeZoneInfoAndOrder() { + ShadowDataFormat.sTimeFormatString = "HH:MM"; + BaseTimeZoneInfoPicker picker = new TestBaseTimeZoneInfoPicker(); + BaseTimeZoneAdapter adapter = picker.createAdapter(mock(TimeZoneData.class)); + Truth.assertThat(adapter.getItemCount()).isEqualTo(2); + + BaseTimeZoneAdapter.AdapterItem item1 = adapter.getItem(0); + Truth.assertThat(item1.getTitle().toString()).isEqualTo("Los Angeles"); + Truth.assertThat(item1.getSummary().toString()).isEqualTo("Pacific Time (GMT-08:00)"); + Truth.assertThat(item1.getCurrentTime()) + .hasLength(ShadowDataFormat.sTimeFormatString.length()); + + BaseTimeZoneAdapter.AdapterItem item2 = adapter.getItem(1); + Truth.assertThat(item2.getTitle().toString()).isEqualTo("New York"); + Truth.assertThat(item2.getSummary().toString()).isEqualTo("Eastern Time (GMT-05:00)"); + Truth.assertThat(item2.getCurrentTime()) + .hasLength(ShadowDataFormat.sTimeFormatString.length()); + } + + public static class TestBaseTimeZoneInfoPicker extends BaseTimeZoneInfoPicker { + + public TestBaseTimeZoneInfoPicker() { + super(0, 0, false, false); + } + + @Override + public List getAllTimeZoneInfos(TimeZoneData timeZoneData) { + TimeZoneInfo zone1 = new TimeZoneInfo.Builder( + TimeZone.getFrozenTimeZone("America/Los_Angeles")) + .setGenericName("Pacific Time") + .setStandardName("Pacific Standard Time") + .setDaylightName("Pacific Daylight Time") + .setExemplarLocation("Los Angeles") + .setGmtOffset("GMT-08:00") + .setItemId(0) + .build(); + TimeZoneInfo zone2 = new TimeZoneInfo.Builder( + TimeZone.getFrozenTimeZone("America/New_York")) + .setGenericName("Eastern Time") + .setStandardName("Eastern Standard Time") + .setDaylightName("Eastern Daylight Time") + .setExemplarLocation("New York") + .setGmtOffset("GMT-05:00") + .setItemId(1) + .build(); + + return Arrays.asList(zone1, zone2); + } + + // Make the method public + @Override + public BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) { + return super.createAdapter(timeZoneData); + } + + @Override + protected Locale getLocale() { + return Locale.US; + } + + @Override + public Context getContext() { + return RuntimeEnvironment.application; + } + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java new file mode 100644 index 00000000000..1c555b078ca --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java @@ -0,0 +1,56 @@ +/* + * 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.datetime.timezone; + +import com.android.settings.datetime.timezone.model.TimeZoneData; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import libcore.util.CountryZonesFinder; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +public class FixedOffsetPickerTest { + + @Test + public void getAllTimeZoneInfos_containsUtcAndGmtZones() { + List regionList = Collections.emptyList(); + CountryZonesFinder finder = mock(CountryZonesFinder.class); + when(finder.lookupAllCountryIsoCodes()).thenReturn(regionList); + + FixedOffsetPicker picker = new FixedOffsetPicker() { + @Override + protected Locale getLocale() { + return Locale.US; + } + }; + List infos = picker.getAllTimeZoneInfos(new TimeZoneData(finder)); + List tzIds = infos.stream().map(info -> info.getId()).collect(Collectors.toList()); + tzIds.contains("Etc/Utc"); + tzIds.contains("Etc/GMT-12"); + tzIds.contains("Etc/GMT+14"); + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java new file mode 100644 index 00000000000..b2c7f035c36 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java @@ -0,0 +1,59 @@ +/* + * 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.datetime.timezone; + +import com.android.settings.datetime.timezone.BaseTimeZoneAdapter.AdapterItem; +import com.android.settings.datetime.timezone.model.TimeZoneData; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import libcore.util.CountryZonesFinder; + +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +public class RegionSearchPickerTest { + + @Test + public void createAdapter_matchRegionName() { + List regionList = new ArrayList(); + regionList.add("US"); + CountryZonesFinder finder = mock(CountryZonesFinder.class); + when(finder.lookupAllCountryIsoCodes()).thenReturn(regionList); + + RegionSearchPicker picker = new RegionSearchPicker() { + @Override + protected Locale getLocale() { + return Locale.US; + } + }; + BaseTimeZoneAdapter adapter = picker.createAdapter(new TimeZoneData(finder)); + assertEquals(1, adapter.getItemCount()); + AdapterItem item = adapter.getItem(0); + assertEquals("United States", item.getTitle().toString()); + assertThat(Arrays.asList(item.getSearchKeys())).contains("United States"); + } +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePickerTest.java new file mode 100644 index 00000000000..e5272704ecb --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/RegionZonePickerTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.datetime.timezone; + +import android.icu.text.Collator; + +import com.android.settings.datetime.timezone.RegionZonePicker.TimeZoneInfoComparator; +import com.android.settings.datetime.timezone.TimeZoneInfo.Formatter; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +public class RegionZonePickerTest { + + @Test + public void compareTimeZoneInfo_matchGmtOrder() { + Date now = new Date(0); // 00:00 1, Jan 1970 + Formatter formatter = new Formatter(Locale.US, now); + TimeZoneInfo timeZone1 = formatter.format("Pacific/Honolulu"); + TimeZoneInfo timeZone2 = formatter.format("America/Los_Angeles"); + TimeZoneInfo timeZone3 = formatter.format("America/Indiana/Marengo"); + TimeZoneInfo timeZone4 = formatter.format("America/New_York"); + + TimeZoneInfoComparator comparator = + new TimeZoneInfoComparator(Collator.getInstance(Locale.US), now); + + // Verify the sorted order + List list = Arrays.asList(timeZone4, timeZone2, timeZone3, timeZone1); + Collections.sort(list, comparator); + assertThat(list).isEqualTo(Arrays.asList(timeZone1, timeZone2, timeZone3, timeZone4)); + } + +} diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/SpannableUtilTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/SpannableUtilTest.java new file mode 100644 index 00000000000..55179070b58 --- /dev/null +++ b/tests/robotests/src/com/android/settings/datetime/timezone/SpannableUtilTest.java @@ -0,0 +1,40 @@ +/* + * 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.datetime.timezone; + +import android.text.Spannable; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(SettingsRobolectricTestRunner.class) +public class SpannableUtilTest { + + @Test + public void testFormat() { + Spannable spannable = SpannableUtil.getResourcesText( + RuntimeEnvironment.application.getResources(), R.string.zone_info_offset_and_name, + "GMT+00:00", "UTC"); + assertThat(spannable.toString()).isEqualTo("UTC (GMT+00:00)"); + } +} From 0ced3704270fbc5d2713102617f49e8bdeff1294 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 5 Mar 2018 14:22:46 -0800 Subject: [PATCH 04/17] Update pref fragment for "see all" in app & notification Instead of launching intent, now to launches all app list as a subsetting. This is more consistent with the other setting pages and fixes a task affinity issue. Change-Id: I0464a2b8f46d46f06bcf297c0f876ae12a7c490b Fixes: 74205806 Test: robotests --- res/xml/app_and_notification.xml | 11 +++-------- .../manageapplications/ManageApplications.java | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml index 05739b877b2..85a766e2f31 100644 --- a/res/xml/app_and_notification.xml +++ b/res/xml/app_and_notification.xml @@ -32,19 +32,14 @@ - - - + android:fragment="com.android.settings.applications.manageapplications.ManageApplications" + android:order="20" /> + android:order="-190" /> Date: Tue, 6 Mar 2018 09:08:29 -0800 Subject: [PATCH 05/17] Fix tests Change-Id: I8b750a87e6852d60ac2f8f61a955b1296cef759b Fixes: 74234076 Test: robotests --- .../robotests/res/xml-mcc998/location_settings.xml | 14 ++++++++++---- .../robotests/res/xml-mcc999/location_settings.xml | 3 +-- .../search/BaseSearchIndexProviderTest.java | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/robotests/res/xml-mcc998/location_settings.xml b/tests/robotests/res/xml-mcc998/location_settings.xml index 993af8676d9..f61ca63d68a 100644 --- a/tests/robotests/res/xml-mcc998/location_settings.xml +++ b/tests/robotests/res/xml-mcc998/location_settings.xml @@ -18,14 +18,20 @@ + android:title="screen_title"> + + \ No newline at end of file diff --git a/tests/robotests/res/xml-mcc999/location_settings.xml b/tests/robotests/res/xml-mcc999/location_settings.xml index 5619c774a6d..de77bfae006 100644 --- a/tests/robotests/res/xml-mcc999/location_settings.xml +++ b/tests/robotests/res/xml-mcc999/location_settings.xml @@ -17,8 +17,7 @@ + android:title="screen_title"> controllers = provider.getPreferenceControllers(mContext); - assertThat(controllers).hasSize(3); + assertThat(controllers).hasSize(2); } public static class NotAvailablePreferenceController From 05811609c763caf3cf145d9ee112f253114387e9 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 6 Mar 2018 10:55:40 -0800 Subject: [PATCH 06/17] Create a new list when building suggestion data. - using sublist of the original suggestions list may results in ConcurrentModificationException if the originaly list is being modified. Create a new list instead to avoid running into the issue. Change-Id: Ia83a0432be542eeb428d177f6118d26fc2262e93 Fixes: 74194336 Test: run monkey --- src/com/android/settings/dashboard/DashboardAdapter.java | 4 ++-- src/com/android/settings/dashboard/DashboardData.java | 6 +++++- .../android/settings/dashboard/DashboardAdapterTest.java | 3 +-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index 99de73eb075..9d9058dfee0 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -155,8 +155,8 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestionsToShow = new ArrayList<>(MAX_SUGGESTION_COUNT); + for (int i = 0; i < MAX_SUGGESTION_COUNT; i++) { + suggestionsToShow.add(suggestions.get(i)); + } + return suggestionsToShow; } /** diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java index e28e89d3d87..e45b02c3db0 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java @@ -124,10 +124,9 @@ public class DashboardAdapterTest { final Suggestion suggestionToRemove = suggestions.get(1); adapter.onSuggestionClosed(suggestionToRemove); - assertThat(adapter.mDashboardData).isEqualTo(dashboardData); assertThat(suggestions.size()).isEqualTo(2); assertThat(suggestions.contains(suggestionToRemove)).isFalse(); - verify(adapter, never()).notifyDashboardDataChanged(any()); + verify(adapter).notifyDashboardDataChanged(any()); } @Test From d2a8b1ff021bd9bdee0ab49a7d1610302e640dc5 Mon Sep 17 00:00:00 2001 From: Alex Cruz Date: Fri, 20 Oct 2017 19:26:58 -0400 Subject: [PATCH 07/17] Change encryption preference's summary accordingly With this change a summary to the encryption preference was added. https://android.googlesource.com/platform/packages/apps/Settings/+/415ce9a77d44732966fbab641d1ecaa8e8688381 The problem is that even if you're not encrypted, it says that your phone is. Is very misleading to the user that don't bother to enter the actual preference and want to see if they are. This fixes that. This is cherry-picked from https://android-review.googlesource.com/#/c/platform/packages/apps/Settings/+/517875/ Test: Go into Settings/Security & Location Fixes: 68691666 Fixes: 68542516 Change-Id: Iaeaffd340f2e248748f244a947aa71eb8ad4ab94 --- res/values/strings.xml | 2 ++ .../security/EncryptionStatusPreferenceController.java | 2 +- .../security/EncryptionStatusPreferenceControllerTest.java | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 4e81b7a129c..d9b0adf8ca0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -819,8 +819,10 @@ Encryption & credentials Phone encrypted + Phone not encrypted Device encrypted + Device not encrypted Lock screen preferences diff --git a/src/com/android/settings/security/EncryptionStatusPreferenceController.java b/src/com/android/settings/security/EncryptionStatusPreferenceController.java index 0978935a49b..234124827b5 100644 --- a/src/com/android/settings/security/EncryptionStatusPreferenceController.java +++ b/src/com/android/settings/security/EncryptionStatusPreferenceController.java @@ -56,7 +56,7 @@ public class EncryptionStatusPreferenceController extends BasePreferenceControll if (TextUtils.equals(getPreferenceKey(), PREF_KEY_ENCRYPTION_DETAIL_PAGE)) { preference.setFragment(CryptKeeperSettings.class.getName()); } - preference.setSummary(R.string.summary_placeholder); + preference.setSummary(R.string.decryption_settings_summary); } } diff --git a/tests/robotests/src/com/android/settings/security/EncryptionStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/EncryptionStatusPreferenceControllerTest.java index 6f736b1246e..ee6d33aace0 100644 --- a/tests/robotests/src/com/android/settings/security/EncryptionStatusPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/security/EncryptionStatusPreferenceControllerTest.java @@ -84,7 +84,7 @@ public class EncryptionStatusPreferenceControllerTest { mController.updateState(mPreference); - final CharSequence summary = mContext.getText(R.string.summary_placeholder); + final CharSequence summary = mContext.getText(R.string.decryption_settings_summary); assertThat(mPreference.getSummary()).isEqualTo(summary); assertThat(mController.getPreferenceKey()).isNotEqualTo(PREF_KEY_ENCRYPTION_SECURITY_PAGE); assertThat(mPreference.getFragment()).isEqualTo(CryptKeeperSettings.class.getName()); @@ -98,7 +98,7 @@ public class EncryptionStatusPreferenceControllerTest { mController.updateState(mPreference); - final CharSequence summary = mContext.getText(R.string.summary_placeholder); + final CharSequence summary = mContext.getText(R.string.decryption_settings_summary); assertThat(mPreference.getSummary()).isEqualTo(summary); assertThat(mPreference.getFragment()).isNotEqualTo(CryptKeeperSettings.class.getName()); From b109d2e4e0b33dc7fca961dabc44e85213220fd5 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Tue, 6 Mar 2018 11:59:37 -0800 Subject: [PATCH 08/17] Import translations. DO NOT MERGE Change-Id: Ia6564078c6d51196f13770bbeb896503e7333c19 Auto-generated-cl: translation import --- res/values-en-rCA/arrays.xml | 14 ++++++----- res/values-en-rCA/strings.xml | 44 +++++++++++++---------------------- res/values-en-rXC/arrays.xml | 14 ++++++----- res/values-en-rXC/strings.xml | 32 ++++++++----------------- res/values-fr/arrays.xml | 14 ++++++----- res/values-fr/strings.xml | 32 ++++++++----------------- res/values-pt-rBR/arrays.xml | 14 ++++++----- res/values-pt-rBR/strings.xml | 32 ++++++++----------------- 8 files changed, 78 insertions(+), 118 deletions(-) diff --git a/res/values-en-rCA/arrays.xml b/res/values-en-rCA/arrays.xml index 9546b09cf5f..fe5e71e4910 100644 --- a/res/values-en-rCA/arrays.xml +++ b/res/values-en-rCA/arrays.xml @@ -138,12 +138,14 @@ "1 hour" "Never time out" - - - - - - + + "Use System Default: %1$d" + "1" + "2" + "3" + "4" + "5" + "Poor" "Poor" diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml index aa5af663b73..a3d5b54105d 100644 --- a/res/values-en-rCA/strings.xml +++ b/res/values-en-rCA/strings.xml @@ -291,10 +291,10 @@ "Settings" "Settings shortcut" "No matching activities found." - "Aeroplane mode" + "Airplane mode" "More" "Wireless & networks" - "Manage Wi‑Fi, Bluetooth, aeroplane mode, mobile networks, & VPNs" + "Manage Wi‑Fi, Bluetooth, airplane mode, mobile networks, & VPNs" "Mobile data" "Calls" "SMS messages" @@ -750,7 +750,7 @@ "Turning off Wi‑Fi…" "Error" "5 GHz band not available in this country" - "In aeroplane mode" + "In Airplane mode" "Open network notification" "Notify when a high‑quality public network is available" "Turn on Wi‑Fi automatically" @@ -925,7 +925,7 @@ "Wi‑Fi hotspot setup" "AndroidAP WPA2 PSK hotspot" "Android Hotspot" - "Unavailable because aeroplane mode is turned on" + "Unavailable because airplane mode is turned on" "Wi-Fi Calling" "Extend call coverage with Wi‑Fi" "Turn on Wi‑Fi calling" @@ -1443,8 +1443,7 @@ "Location for work profile" "App-level permissions" "Recent location requests" - - + "See all" "No apps have requested location recently" "Location services" "High battery use" @@ -2160,7 +2159,7 @@ "Battery used when tablet is idle" "Battery used when phone is idle" "Battery used by mobile radio" - "Switch to aeroplane mode to save power in areas with no mobile coverage" + "Switch to airplane mode to save power in areas with no cell coverage" "Battery used by the flashlight" "Battery used by the camera" "Battery used by the display and backlight" @@ -2317,8 +2316,7 @@ "Before you can use credential storage, your device needs to have a secure lock screen" "SET LOCK" "Apps with usage access" - - + "Emergency dialling signal" "Set behaviour when an emergency call is placed" "Backup" "Backup & restore" @@ -2927,12 +2925,9 @@ "Dock speaker plays" "All audio" "Media audio only" - - - - - - + "Silence" + "Tones" + "Vibrations" "Power on sounds" "Never" @@ -3112,9 +3107,7 @@ "Choose rule type" "Delete \"%1$s\" rule?" "Delete" - "Rule type" "Unknown" - "Configure rule" "These settings can\'t be changed at the moment. An app (%1$s) has automatically turned on Do Not Disturb with custom behaviour." "These settings can\'t be changed at the moment. An app has automatically turned on Do Not Disturb with custom behaviour." "These settings can\'t be changed at the moment. Do Not Disturb was manually turned on with custom behaviour." @@ -3512,7 +3505,7 @@ "Hide" "Hotspot is on" "Portable Wi-Fi hotspot %1$s is active, Wi-Fi for this device is turned off." - "Aeroplane mode is on" + "Airplane mode is on" "Wi-Fi, Bluetooth and mobile network are turned off. You can\'t make phone calls or connect to the Internet." "Do Not Disturb is on" "Battery Saver is on" @@ -3863,14 +3856,9 @@ "Use Bluetooth when driving" "Turn on Bluetooth automatically when driving" "See Android 8.0 battery settings" - - - - - - - - - - + "Toggle Wi-Fi on and off" + "toggle Wi-Fi on and off" + "Toggle Wi-Fi on and off" + "Allow toggle Wi-Fi" + "Allow this app to change Wi-Fi state including connecting to Wi-Fi and turing Wi-Fi on and off." diff --git a/res/values-en-rXC/arrays.xml b/res/values-en-rXC/arrays.xml index a04f1ad93ca..3dfcf58e8e1 100644 --- a/res/values-en-rXC/arrays.xml +++ b/res/values-en-rXC/arrays.xml @@ -138,12 +138,14 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎1 hour‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎Never time out‎‏‎‎‏‎" - - - - - - + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎Use System Default: ‎‏‎‎‏‏‎%1$d‎‏‎‎‏‏‏‎‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‎‏‎1‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎2‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎3‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎4‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎5‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎Poor‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‏‏‎Poor‎‏‎‎‏‎" diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml index 0c46a91e8c2..3568cbfd54b 100644 --- a/res/values-en-rXC/strings.xml +++ b/res/values-en-rXC/strings.xml @@ -1442,8 +1442,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎Location for work profile‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎App-level permissions‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎Recent location requests‎‏‎‎‏‎" - - + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎See all‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎No apps have requested location recently‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎Location services‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎High battery use‎‏‎‎‏‎" @@ -2316,8 +2315,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‎‎‎Before you can use credential storage, your device need to have a secure lock screen‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‏‎SET LOCK‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎Apps with usage access‎‏‎‎‏‎" - - + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‎Emergency dialing signal‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‎Set behavior when an emergency call is placed‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‎Backup‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎Backup & restore‎‏‎‎‏‎" @@ -2926,12 +2924,9 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎Dock speaker plays‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎All audio‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‎Media audio only‎‏‎‎‏‎" - - - - - - + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎Silence‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎Tones‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‏‎Vibrations‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‎Power on sounds‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎Never‎‏‎‎‏‎" @@ -3111,9 +3106,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‎Choose rule type‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‏‎Delete “‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎” rule?‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎Delete‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎Rule type‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‎Unknown‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎‎‏‎Configure rule‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‎These settings can\'t be changed right now. An app (‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎) has automatically turned on Do Not Disturb with custom behavior.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎These settings can\'t be changed right now. An app has automatically turned on Do Not Disturb with custom behavior.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎These settings can\'t be changed right now. Do Not Disturb was manually turned on with custom behavior.‎‏‎‎‏‎" @@ -3862,14 +3855,9 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎Use Bluetooth when driving‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‎Turn on Bluetooth automatically when driving‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎See Android 8.0 battery settings‎‏‎‎‏‎" - - - - - - - - - - + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎Toggle wifi on and off‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‏‎toggle wifi on and off‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‎Toggle wifi on and off‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‎‏‎Allow toggle wifi‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‎‎Allow this app to change wifi state including connecting to wifi and turing wifi on and off.‎‏‎‎‏‎" diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml index ca1ae442480..fbe981dbf1c 100644 --- a/res/values-fr/arrays.xml +++ b/res/values-fr/arrays.xml @@ -138,12 +138,14 @@ "Une heure" "Aucun délai" - - - - - - + + "Utiliser la valeur par défaut du système : %1$d" + "1" + "2" + "3" + "4" + "5" + "Faible" "Faible" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 0bf750ef7d7..68c374c0932 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -1443,8 +1443,7 @@ "Emplacement du profil pro" "Autorisations au niveau applis" "Demandes de localisation récentes" - - + "Tout afficher" "Aucune demande d\'accès à votre position n\'a récemment été envoyée pour une application." "Services de localisation" "Forte utilisation de la batterie" @@ -2317,8 +2316,7 @@ "Avant de pouvoir utiliser le stockage des identifiants, vous devez définir un écran de verrouillage sécurisé sur votre appareil" "DÉFINIR VERROUILLAGE" "Applis accédant aux infos utilisation" - - + "Signal de numérotation d\'urgence" "Définir le comportement en cas d\'appel d\'urgence" "Sauvegarde" "Sauvegarde et restauration" @@ -2927,12 +2925,9 @@ "Sons haut-parleurs sta. accueil" "Tout l\'audio" "Audio des contenus multimédias seulement" - - - - - - + "Silence" + "Tonalités" + "Vibrations" "Sons de mise en route" "Jamais" @@ -3112,9 +3107,7 @@ "Sélectionner le type de règle" "Supprimer la règle \"%1$s\" ?" "Supprimer" - "Type de règle" "Inconnu" - "Configurer une règle" "Vous ne pouvez pas modifier ces paramètres pour le moment. Une application (%1$s) a activé le mode Ne pas déranger automatiquement avec un comportement personnalisé." "Vous ne pouvez pas modifier ces paramètres pour le moment. Une application a activé le mode Ne pas déranger automatiquement avec un comportement personnalisé." "Vous ne pouvez pas modifier ces paramètres pour le moment. Le mode Ne pas déranger a été activé automatiquement avec un comportement personnalisé." @@ -3863,14 +3856,9 @@ "Utiliser le Bluetooth en conduisant" "Activer automatiquement le Bluetooth lorsque vous conduisez" "Afficher les paramètres de batterie d\'Android 8.0" - - - - - - - - - - + "Activer/Désactiver le Wi-Fi" + "activer/désactiver le Wi-Fi" + "Activer/Désactiver le Wi-Fi" + "Autoriser l\'activation et la désactivation du Wi-Fi" + "Autoriser cette application à modifier l\'état du Wi-Fi, y compris la connexion au Wi-Fi, ainsi que l\'activation et la désactivation de ce dernier" diff --git a/res/values-pt-rBR/arrays.xml b/res/values-pt-rBR/arrays.xml index 72ec75e9318..55420ebabb7 100644 --- a/res/values-pt-rBR/arrays.xml +++ b/res/values-pt-rBR/arrays.xml @@ -138,12 +138,14 @@ "Uma hora" "Nunca definir tempo limite" - - - - - - + + "Usar padrão do sistema: %1$d" + "1" + "2" + "3" + "4" + "5" + "Ruim" "Fraca" diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml index a437cce5fb0..eea65f4b031 100644 --- a/res/values-pt-rBR/strings.xml +++ b/res/values-pt-rBR/strings.xml @@ -1443,8 +1443,7 @@ "Local do perfil de trabalho" "Permissões no nível do app" "Solicitações recentes" - - + "Ver tudo" "Nenhum app solicitou a localização recentemente" "Serviços de localização" "Uso da bateria elevado" @@ -2317,8 +2316,7 @@ "Para que você possa usar o armazenamento de credenciais, seu dispositivo precisa ter uma tela de bloqueio" "DEFINIR BLOQUEIO" "Apps com acesso ao uso" - - + "Sinal de discagem de emergência" "Definir comportamento durante uma chamada de emergência" "Backup" "Backup e restauração" @@ -2927,12 +2925,9 @@ "Repr. de alto-falante em dock" "Todo o áudio" "Somente áudio de mídia" - - - - - - + "Silêncio" + "Tons" + "Vibrações" "Ativar sons" "Nunca" @@ -3112,9 +3107,7 @@ "Escolher tipo de regra" "Excluir a regra \"%1$s\"?" "Excluir" - "Tipo de regra" "Desconhecido" - "Configurar regra" "Não é possível alterar essas configurações no momento. Um app (%1$s) ativou automaticamente o modo \"Não perturbe\" com comportamento personalizado." "Não é possível alterar essas configurações no momento. Um app ativou automaticamente o modo \"Não perturbe\" com comportamento personalizado." "Não é possível alterar essas configurações no momento. O modo \"Não perturbe\" foi ativado manualmente com comportamento personalizado." @@ -3863,14 +3856,9 @@ "Usar Bluetooth ao dirigir" "Ativar Bluetooth automaticamente ao dirigir" "Ver configurações de bateria do Android 8.0" - - - - - - - - - - + "Ativar e desativar o Wi-Fi" + "ativar e desativar o Wi-Fi" + "Ativar e desativar o Wi-Fi" + "Permitir alternância do Wi-Fi" + "Permitir que esse app altere o estado do Wi-Fi, inclusive se conectando ao Wi-Fi e ativando-o ou desativando-o." From b619e51bafeb687ae308b7d29f46d6bdc9905542 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 6 Mar 2018 11:36:54 -0800 Subject: [PATCH 09/17] Fix crash in battery info settings page. - when receive package updates, check if the buttons preference is available before trying to update the UI. Change-Id: Ie3482b94c84cd55e9a138af4719a031f5167e089 Fixes: 74130214 Test: make RunSettingsRoboTests --- .../AppButtonsPreferenceController.java | 4 +- .../AppButtonsPreferenceControllerTest.java | 59 +++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java index 4094e1ff618..854b855a2c3 100644 --- a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java +++ b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java @@ -297,7 +297,9 @@ public class AppButtonsPreferenceController extends AbstractPreferenceController @Override public void onPackageListChanged() { - refreshUi(); + if (isAvailable()) { + refreshUi(); + } } @Override diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java index d2252a704ed..eebccd8bfd9 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -137,7 +138,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testRetrieveAppEntry_hasAppEntry_notNull() + public void retrieveAppEntry_hasAppEntry_notNull() throws PackageManager.NameNotFoundException { doReturn(mPackageInfo).when(mPackageManger).getPackageInfo(anyString(), anyInt()); @@ -148,7 +149,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testRetrieveAppEntry_noAppEntry_null() throws PackageManager.NameNotFoundException { + public void retrieveAppEntry_noAppEntry_null() throws PackageManager.NameNotFoundException { doReturn(null).when(mState).getEntry(eq(PACKAGE_NAME), anyInt()); doReturn(mPackageInfo).when(mPackageManger).getPackageInfo(anyString(), anyInt()); @@ -159,7 +160,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testRetrieveAppEntry_throwException_null() throws + public void retrieveAppEntry_throwException_null() throws PackageManager.NameNotFoundException { doReturn(mAppEntry).when(mState).getEntry(anyString(), anyInt()); doThrow(new PackageManager.NameNotFoundException()).when(mPackageManger).getPackageInfo( @@ -172,7 +173,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateUninstallButton_isSystemApp_handleAsDisableableButton() { + public void updateUninstallButton_isSystemApp_handleAsDisableableButton() { doReturn(false).when(mController).handleDisableable(); mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; @@ -183,7 +184,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testIsAvailable_nonInstantApp() throws Exception { + public void isAvailable_nonInstantApp() throws Exception { mController.mAppEntry = mAppEntry; ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", new InstantAppDataProvider() { @@ -196,7 +197,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testIsAvailable_instantApp() throws Exception { + public void isAvailable_instantApp() throws Exception { mController.mAppEntry = mAppEntry; ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", new InstantAppDataProvider() { @@ -209,7 +210,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateUninstallButton_isDeviceAdminApp_setButtonDisable() { + public void updateUninstallButton_isDeviceAdminApp_setButtonDisable() { doReturn(true).when(mController).handleDisableable(); mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; doReturn(true).when(mDpm).packageHasActiveAdmins(anyString()); @@ -221,7 +222,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateUninstallButton_isProfileOrDeviceOwner_setButtonDisable() { + public void updateUninstallButton_isProfileOrDeviceOwner_setButtonDisable() { doReturn(true).when(mDpm).isDeviceOwnerAppOnAnyUser(anyString()); mController.updateUninstallButton(); @@ -230,7 +231,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateUninstallButton_isDeviceProvisioningApp_setButtonDisable() { + public void updateUninstallButton_isDeviceProvisioningApp_setButtonDisable() { doReturn(true).when(mDpm).isDeviceOwnerAppOnAnyUser(anyString()); when(mSettingsActivity.getResources().getString(anyInt())).thenReturn(PACKAGE_NAME); @@ -240,7 +241,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateUninstallButton_isUninstallInQueue_setButtonDisable() { + public void updateUninstallButton_isUninstallInQueue_setButtonDisable() { doReturn(true).when(mDpm).isUninstallInQueue(any()); mController.updateUninstallButton(); @@ -249,7 +250,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateUninstallButton_isHomeAppAndBundled_setButtonDisable() { + public void updateUninstallButton_isHomeAppAndBundled_setButtonDisable() { mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM; mController.mHomePackages.add(PACKAGE_NAME); @@ -259,7 +260,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateForceStopButton_HasActiveAdmins_setButtonDisable() { + public void updateForceStopButton_HasActiveAdmins_setButtonDisable() { doReturn(true).when(mDpm).packageHasActiveAdmins(anyString()); mController.updateForceStopButton(); @@ -268,14 +269,14 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testUpdateForceStopButton_AppNotStopped_setButtonEnable() { + public void updateForceStopButton_AppNotStopped_setButtonEnable() { mController.updateForceStopButton(); verify(mController).updateForceStopButtonInner(true); } @Test - public void testUninstallPkg_intentSent() { + public void uninstallPkg_intentSent() { mController.uninstallPkg(PACKAGE_NAME, ALL_USERS, DISABLE_AFTER_INSTALL); verify(mFragment).startActivityForResult(any(), eq(REQUEST_UNINSTALL)); @@ -287,7 +288,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testForceStopPackage_methodInvokedAndUpdated() { + public void forceStopPackage_methodInvokedAndUpdated() { final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); doReturn(appEntry).when(mState).getEntry(anyString(), anyInt()); doNothing().when(mController).updateForceStopButton(); @@ -300,7 +301,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testHandleDisableable_isHomeApp_notControllable() { + public void handleDisableable_isHomeApp_notControllable() { mController.mHomePackages.add(PACKAGE_NAME); final boolean controllable = mController.handleDisableable(); @@ -310,7 +311,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testHandleDisableable_isAppEnabled_controllable() { + public void handleDisableable_isAppEnabled_controllable() { mAppEntry.info.enabled = true; mAppEntry.info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; doReturn(false).when(mController).isSystemPackage(any(), any(), any()); @@ -322,7 +323,7 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testHandleDisableable_isAppDisabled_controllable() { + public void handleDisableable_isAppDisabled_controllable() { mAppEntry.info.enabled = false; mAppEntry.info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; doReturn(false).when(mController).isSystemPackage(any(), any(), any()); @@ -334,13 +335,33 @@ public class AppButtonsPreferenceControllerTest { } @Test - public void testRefreshUi_packageNull_shouldNotCrash() { + public void refreshUi_packageNull_shouldNotCrash() { mController.mPackageName = null; // Should not crash in this method assertThat(mController.refreshUi()).isFalse(); } + @Test + public void onPackageListChanged_available_shouldRefreshUi() { + doReturn(true).when(mController).isAvailable(); + doReturn(true).when(mController).refreshUi(); + + mController.onPackageListChanged(); + + verify(mController).refreshUi(); + } + + @Test + public void onPackageListChanged_notAvailable_shouldNotRefreshUiAndNoCrash() { + doReturn(false).when(mController).isAvailable(); + + mController.onPackageListChanged(); + + verify(mController, never()).refreshUi(); + // Should not crash in this method + } + /** * The test fragment which implements * {@link ButtonActionDialogFragment.AppButtonsDialogListener} From 155c1785e19be5b1d3054bfae6f0312e273cc482 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 6 Mar 2018 10:28:29 -0800 Subject: [PATCH 10/17] Add auto restriction preference 1. Add method in feature provider to check whether smart battery is supported 2. Hook up above method to controllers 3. Add auto restriction preference 4. Add tests Bug: 73002171 Test: RunSettingsRoboTests Change-Id: Ic314f61ad52e9bedb629b308731cb968bb4b1773 Merged-In: Ic314f61ad52e9bedb629b308731cb968bb4b1773 (cherry picked from commit ab0b0add0a9b7409db016b0f1561612c16173fbd) --- res/values/strings.xml | 5 + res/xml/smart_battery_detail.xml | 10 +- .../AutoRestrictionPreferenceController.java | 65 ++++++++++ .../fuelgauge/PowerUsageFeatureProvider.java | 5 + .../PowerUsageFeatureProviderImpl.java | 5 + .../SmartBatteryPreferenceController.java | 11 +- ...toRestrictionPreferenceControllerTest.java | 115 ++++++++++++++++++ .../PowerUsageFeatureProviderImplTest.java | 5 + .../SmartBatteryPreferenceControllerTest.java | 26 ++++ 9 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 src/com/android/settings/fuelgauge/AutoRestrictionPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/AutoRestrictionPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 4e81b7a129c..011fce6889b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4898,6 +4898,11 @@ %1$d apps + + Restrict apps automatically + + Prevent apps from using extra battery in the background + Stop app? diff --git a/res/xml/smart_battery_detail.xml b/res/xml/smart_battery_detail.xml index a236cb824e1..e7fb9f798f1 100644 --- a/res/xml/smart_battery_detail.xml +++ b/res/xml/smart_battery_detail.xml @@ -15,7 +15,9 @@ limitations under the License. --> - @@ -32,6 +34,12 @@ android:title="@string/smart_battery_title" android:summary="@string/smart_battery_summary"/> + + diff --git a/src/com/android/settings/fuelgauge/AutoRestrictionPreferenceController.java b/src/com/android/settings/fuelgauge/AutoRestrictionPreferenceController.java new file mode 100644 index 00000000000..e8e5ad90611 --- /dev/null +++ b/src/com/android/settings/fuelgauge/AutoRestrictionPreferenceController.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; + +/** + * Controller to change and update the auto restriction toggle + */ +public class AutoRestrictionPreferenceController extends BasePreferenceController implements + Preference.OnPreferenceChangeListener { + private static final String KEY_SMART_BATTERY = "auto_restriction"; + private static final int ON = 1; + private static final int OFF = 0; + private PowerUsageFeatureProvider mPowerUsageFeatureProvider; + + public AutoRestrictionPreferenceController(Context context) { + super(context, KEY_SMART_BATTERY); + mPowerUsageFeatureProvider = FeatureFactory.getFactory( + context).getPowerUsageFeatureProvider(context); + } + + @Override + public int getAvailabilityStatus() { + return mPowerUsageFeatureProvider.isSmartBatterySupported() + ? DISABLED_UNSUPPORTED + : AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + final boolean smartBatteryOn = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.APP_AUTO_RESTRICTION_ENABLED, ON) == ON; + ((SwitchPreference) preference).setChecked(smartBatteryOn); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean smartBatteryOn = (Boolean) newValue; + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.APP_AUTO_RESTRICTION_ENABLED, + smartBatteryOn ? ON : OFF); + return true; + } +} diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java index 79675cb3bd9..861ee5622b5 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java @@ -118,4 +118,9 @@ public interface PowerUsageFeatureProvider { * the caller */ boolean getEarlyWarningSignal(Context context, String id); + + /** + * Checks whether smart battery feature is supported in this device + */ + boolean isSmartBatterySupported(); } diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java index 93c4e032522..6c3897de02a 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java @@ -136,4 +136,9 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider public boolean getEarlyWarningSignal(Context context, String id) { return false; } + + @Override + public boolean isSmartBatterySupported() { + return false; + } } diff --git a/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java index ee5279e027f..8b99a4361a7 100644 --- a/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java +++ b/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java @@ -19,12 +19,11 @@ package com.android.settings.fuelgauge; import android.content.Context; import android.provider.Settings; -import android.support.annotation.VisibleForTesting; import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.Preference; -import com.android.settings.applications.LayoutPreference; import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; /** * Controller to change and update the smart battery toggle @@ -34,15 +33,19 @@ public class SmartBatteryPreferenceController extends BasePreferenceController i private static final String KEY_SMART_BATTERY = "smart_battery"; private static final int ON = 1; private static final int OFF = 0; + private PowerUsageFeatureProvider mPowerUsageFeatureProvider; public SmartBatteryPreferenceController(Context context) { super(context, KEY_SMART_BATTERY); + mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context); } @Override public int getAvailabilityStatus() { - // TODO(b/71502850): get Availability from API. The device may not support it. - return AVAILABLE; + return mPowerUsageFeatureProvider.isSmartBatterySupported() + ? AVAILABLE + : DISABLED_UNSUPPORTED; } @Override diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AutoRestrictionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AutoRestrictionPreferenceControllerTest.java new file mode 100644 index 00000000000..241f5502031 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/AutoRestrictionPreferenceControllerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; + +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AutoRestrictionPreferenceControllerTest { + private static final int ON = 1; + private static final int OFF = 0; + + private AutoRestrictionPreferenceController mController; + private SwitchPreference mPreference; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mContext = RuntimeEnvironment.application; + mController = new AutoRestrictionPreferenceController(mContext); + mPreference = new SwitchPreference(mContext); + } + + @Test + public void testUpdateState_AutoRestrictionOn_preferenceChecked() { + putAutoRestrictionValue(ON); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void testUpdateState_AutoRestrictionOff_preferenceUnchecked() { + putAutoRestrictionValue(OFF); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void testUpdateState_checkPreference_autoRestrictionOn() { + mController.onPreferenceChange(mPreference, true); + + assertThat(getAutoRestrictionValue()).isEqualTo(ON); + } + + @Test + public void testUpdateState_unCheckPreference_autoRestrictionOff() { + mController.onPreferenceChange(mPreference, false); + + assertThat(getAutoRestrictionValue()).isEqualTo(OFF); + } + + @Test + public void testGetAvailabilityStatus_smartBatterySupported_returnDisabled() { + doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isSmartBatterySupported(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.DISABLED_UNSUPPORTED); + } + + @Test + public void testGetAvailabilityStatus_smartBatteryUnSupported_returnAvailable() { + doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isSmartBatterySupported(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + private void putAutoRestrictionValue(int value) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.APP_AUTO_RESTRICTION_ENABLED, + value); + } + + private int getAutoRestrictionValue() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.APP_AUTO_RESTRICTION_ENABLED, ON); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java index f37d607530c..70acb73b05f 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java @@ -136,4 +136,9 @@ public class PowerUsageFeatureProviderImplTest { public void testIsPowerAccountingToggleEnabled_returnTrue() { assertThat(mPowerFeatureProvider.isPowerAccountingToggleEnabled()).isTrue(); } + + @Test + public void testIsSmartBatterySupported_returnFalse() { + assertThat(mPowerFeatureProvider.isSmartBatterySupported()).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java index 494d563b4d2..fbbc6f9e612 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java @@ -18,10 +18,16 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; + import android.content.ContentResolver; +import android.content.Context; import android.provider.Settings; import android.support.v14.preference.SwitchPreference; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.FakeFeatureFactory; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,11 +44,15 @@ public class SmartBatteryPreferenceControllerTest { private SmartBatteryPreferenceController mController; private SwitchPreference mPreference; private ContentResolver mContentResolver; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Before public void setUp() { MockitoAnnotations.initMocks(this); + mFeatureFactory = FakeFeatureFactory.setupForTest(); mContentResolver = RuntimeEnvironment.application.getContentResolver(); mController = new SmartBatteryPreferenceController(RuntimeEnvironment.application); mPreference = new SwitchPreference(RuntimeEnvironment.application); @@ -80,6 +90,22 @@ public class SmartBatteryPreferenceControllerTest { assertThat(getSmartBatteryValue()).isEqualTo(OFF); } + @Test + public void testGetAvailabilityStatus_smartBatterySupported_returnAvailable() { + doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isSmartBatterySupported(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void testGetAvailabilityStatus_smartBatteryUnSupported_returnDisabled() { + doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isSmartBatterySupported(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.DISABLED_UNSUPPORTED); + } + private void putSmartBatteryValue(int value) { Settings.Global.putInt(mContentResolver, Settings.Global.APP_STANDBY_ENABLED, value); } From c08984befdadbfb5eadfe33df722a4dffab3f98d Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 6 Mar 2018 13:37:21 -0800 Subject: [PATCH 11/17] Add intent fields when launching instant app. - add browseable category, package name, and new task flag. Change-Id: Ie558f5885b5f0f78c88241de7c2d7a21cfb9d0fe Fixes: 74131502 Test: make RunSettingsRoboTests --- .../appinfo/InstantAppButtonsPreferenceController.java | 3 +++ .../appinfo/InstantAppButtonsPreferenceControllerTest.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceController.java index df64d76b43d..d2515a37810 100644 --- a/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceController.java @@ -147,7 +147,10 @@ public class InstantAppButtonsPreferenceController extends BasePreferenceControl if (!TextUtils.isEmpty(mLaunchUri)) { installButton.setVisibility(View.GONE); final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.addCategory(Intent.CATEGORY_BROWSABLE); + intent.setPackage(mPackageName); intent.setData(Uri.parse(mLaunchUri)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); launchButton.setOnClickListener(v -> mParent.startActivity(intent)); } else { launchButton.setVisibility(View.GONE); diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceControllerTest.java index 8a85d70a01b..ad0a9fe75f7 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceControllerTest.java @@ -262,6 +262,9 @@ public class InstantAppButtonsPreferenceControllerTest { verify(mFragment).startActivity(argThat(intent-> intent != null && intent.getAction().equals(Intent.ACTION_VIEW) + && intent.hasCategory(Intent.CATEGORY_BROWSABLE) + && (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 + && TextUtils.equals(intent.getPackage(), TEST_AIA_PACKAGE_NAME) && TextUtils.equals(intent.getDataString(), launchUri))); } From 4fdf0b461dbede662bf10bcabe91bd9de852b78b Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 6 Mar 2018 14:16:08 -0800 Subject: [PATCH 12/17] Stop wrapping RoundedHomepageIcon if it's already wrapped. On homepage, if an icon is already wrapped in RoundedHomepageIcon, we shouldn't wrap it more. Otherwise each time we do this it will add a few dp padding around the bitmap. Change-Id: I5313a38f4bd128272d7b4e05209aaee72c690c55 Fixes: 73295342 Test: robotest --- .../settings/dashboard/DashboardAdapter.java | 59 +++++++++---------- .../dashboard/DashboardAdapterTest.java | 53 ++++++++++++----- 2 files changed, 68 insertions(+), 44 deletions(-) diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index 99de73eb075..b2d816d6a62 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -17,7 +17,6 @@ package com.android.settings.dashboard; import android.app.Activity; import android.content.Context; -import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -27,7 +26,6 @@ import android.support.v7.util.DiffUtil; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -56,8 +54,8 @@ import com.android.settingslib.utils.IconCache; import java.util.List; public class DashboardAdapter extends RecyclerView.Adapter - implements SummaryLoader.SummaryConsumer, SuggestionAdapter.Callback, LifecycleObserver, - OnSaveInstanceState { + implements SummaryLoader.SummaryConsumer, SuggestionAdapter.Callback, LifecycleObserver, + OnSaveInstanceState { public static final String TAG = "DashboardAdapter"; private static final String STATE_CATEGORY_LIST = "category_list"; @@ -84,8 +82,8 @@ public class DashboardAdapter extends RecyclerView.Adapter conditions, SuggestionControllerMixin suggestionControllerMixin, - Lifecycle lifecycle) { + List conditions, SuggestionControllerMixin suggestionControllerMixin, + Lifecycle lifecycle) { DashboardCategory category = null; boolean conditionExpanded = false; @@ -96,14 +94,14 @@ public class DashboardAdapter extends RecyclerView.Adapter data) { final DashboardData prevData = mDashboardData; mDashboardData = new DashboardData.Builder(prevData) - .setSuggestions(data) - .build(); + .setSuggestions(data) + .build(); notifyDashboardDataChanged(prevData); } @@ -130,8 +128,8 @@ public class DashboardAdapter extends RecyclerView.Adapter { mMetricsFeatureProvider.action(mContext, - MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false); + MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false); DashboardData prevData = mDashboardData; mDashboardData = new DashboardData.Builder(prevData). - setConditionExpanded(false).build(); + setConditionExpanded(false).build(); notifyDashboardDataChanged(prevData); scrollToTopOfConditions(); }); @@ -254,7 +252,7 @@ public class DashboardAdapter extends RecyclerView.Adapter { mMetricsFeatureProvider.action(mContext, - MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true); + MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true); final DashboardData prevData = mDashboardData; mDashboardData = new DashboardData.Builder(prevData) - .setConditionExpanded(true).build(); + .setConditionExpanded(true).build(); notifyDashboardDataChanged(prevData); scrollToTopOfConditions(); }); @@ -291,8 +289,8 @@ public class DashboardAdapter extends RecyclerView.Adapter) mDashboardData.getItemEntityByPosition(position), - mDashboardData.isConditionExpanded()); + (List) mDashboardData.getItemEntityByPosition(position), + mDashboardData.isConditionExpanded()); adapter.addDismissHandling(holder.data); holder.data.setAdapter(adapter); holder.data.setLayoutManager(new LinearLayoutManager(mContext)); @@ -303,7 +301,7 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestions = - (List) mDashboardData.getItemEntityByPosition(position); + (List) mDashboardData.getItemEntityByPosition(position); if (suggestions != null && suggestions.size() > 0) { mSuggestionAdapter.setSuggestions(suggestions); holder.data.setAdapter(mSuggestionAdapter); @@ -316,7 +314,8 @@ public class DashboardAdapter extends RecyclerView.Adapter suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3"); adapter.setSuggestions(suggestions); @@ -114,7 +115,7 @@ public class DashboardAdapterTest { when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class)); when(itemView.findViewById(android.R.id.title)).thenReturn(mock(TextView.class)); final DashboardAdapter.SuggestionContainerHolder holder = - new DashboardAdapter.SuggestionContainerHolder(itemView); + new DashboardAdapter.SuggestionContainerHolder(itemView); adapter.onBindSuggestion(holder, 0); @@ -133,8 +134,9 @@ public class DashboardAdapterTest { @Test public void testSuggestionDismissed_onlySuggestion_updateDashboardData() { DashboardAdapter adapter = - spy(new DashboardAdapter(mContext, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */)); + spy(new DashboardAdapter(mContext, null /* savedInstanceState */, + null /* conditions */, null /* suggestionControllerMixin */, + null /* lifecycle */)); final List suggestions = makeSuggestionsV2("pkg1"); adapter.setSuggestions(suggestions); final DashboardData dashboardData = adapter.mDashboardData; @@ -149,7 +151,7 @@ public class DashboardAdapterTest { @Test public void testBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() { mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); final List suggestions = makeSuggestionsV2("pkg1"); mDashboardAdapter.setSuggestions(suggestions); @@ -163,7 +165,7 @@ public class DashboardAdapterTest { when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class)); when(itemView.findViewById(android.R.id.title)).thenReturn(mock(TextView.class)); final DashboardAdapter.SuggestionContainerHolder holder = - new DashboardAdapter.SuggestionContainerHolder(itemView); + new DashboardAdapter.SuggestionContainerHolder(itemView); mDashboardAdapter.onBindSuggestion(holder, 0); @@ -176,14 +178,14 @@ public class DashboardAdapterTest { final Context context = RuntimeEnvironment.application; final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); final DashboardAdapter.DashboardItemHolder holder = - new DashboardAdapter.DashboardItemHolder(view); + new DashboardAdapter.DashboardItemHolder(view); final Tile tile = new Tile(); tile.icon = Icon.createWithResource(context, R.drawable.ic_settings); final IconCache iconCache = mock(IconCache.class); when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings)); mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); mDashboardAdapter.onBindTile(holder, tile); @@ -191,11 +193,11 @@ public class DashboardAdapterTest { } @Test - public void onBindTile_externalTile_shouldNotUseGenericBackgroundIcon() { + public void onBindTile_externalTile_shouldUpdateIcon() { final Context context = RuntimeEnvironment.application; final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); final DashboardAdapter.DashboardItemHolder holder = - new DashboardAdapter.DashboardItemHolder(view); + new DashboardAdapter.DashboardItemHolder(view); final Tile tile = new Tile(); tile.icon = mock(Icon.class); when(tile.icon.getResPackage()).thenReturn("another.package"); @@ -204,13 +206,36 @@ public class DashboardAdapterTest { when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings)); mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); + mDashboardAdapter.onBindTile(holder, tile); verify(iconCache).updateIcon(eq(tile.icon), any(RoundedHomepageIcon.class)); } + @Test + public void onBindTile_externalTile_usingRoundedHomepageIcon_shouldNotUpdateIcon() { + final Context context = RuntimeEnvironment.application; + final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); + final DashboardAdapter.DashboardItemHolder holder = + new DashboardAdapter.DashboardItemHolder(view); + final Tile tile = new Tile(); + tile.icon = mock(Icon.class); + when(tile.icon.getResPackage()).thenReturn("another.package"); + + final IconCache iconCache = mock(IconCache.class); + when(iconCache.getIcon(tile.icon)).thenReturn(mock(RoundedHomepageIcon.class)); + + mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, + null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); + + mDashboardAdapter.onBindTile(holder, tile); + + verify(iconCache, never()).updateIcon(eq(tile.icon), any(RoundedHomepageIcon.class)); + } + private List makeSuggestionsV2(String... pkgNames) { final List suggestions = new ArrayList<>(); for (String pkgName : pkgNames) { From 7b59cde2ceefee763b8f1bcbb31b80c68d53f884 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 6 Mar 2018 15:15:01 -0800 Subject: [PATCH 13/17] Add strings for audio output switcher. Bug: 74130772 Test: rebuild Change-Id: I84b702b69cc0cd23caeebb441ad8310b56441b5b --- res/values/strings.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 4e81b7a129c..ef72ae38a3f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9387,4 +9387,22 @@ Allow this app to change wifi state including connecting to wifi and turing wifi on and off. + + Play media to + + + Phone + + + Tablet + + + Device + + + Unavailable during calls + + + Unavailable + From d0086188d204c8c24834c03bef5e3578ecaaa509 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 27 Feb 2018 14:12:32 -0800 Subject: [PATCH 14/17] Change current PowerUsageAdvanced to legacy code In future cl, we will create new PowerUsageAdvanced page. Bug: 73300636 Test: Build Change-Id: I608f6b1fb9bf29a6ef5d7f9d67bcb6eea9e7aec7 Merged-In: I608f6b1fb9bf29a6ef5d7f9d67bcb6eea9e7aec7 --- ..._advanced.xml => power_usage_advanced_legacy.xml} | 6 +++--- ...geAdvanced.java => PowerUsageAdvancedLegacy.java} | 12 ++++++------ .../settings/fuelgauge/PowerUsageSummaryLegacy.java | 2 +- .../search/SearchIndexableResourcesImpl.java | 4 ++-- ...edTest.java => PowerUsageAdvancedLegacyTest.java} | 11 +++++------ 5 files changed, 17 insertions(+), 18 deletions(-) rename res/xml/{power_usage_advanced.xml => power_usage_advanced_legacy.xml} (88%) rename src/com/android/settings/fuelgauge/{PowerUsageAdvanced.java => PowerUsageAdvancedLegacy.java} (98%) rename tests/robotests/src/com/android/settings/fuelgauge/{PowerUsageAdvancedTest.java => PowerUsageAdvancedLegacyTest.java} (98%) diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced_legacy.xml similarity index 88% rename from res/xml/power_usage_advanced.xml rename to res/xml/power_usage_advanced_legacy.xml index b31eb403db1..26be727c038 100644 --- a/res/xml/power_usage_advanced.xml +++ b/res/xml/power_usage_advanced_legacy.xml @@ -17,15 +17,15 @@ + android:key="battery_graph_legacy"/> diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvancedLegacy.java similarity index 98% rename from src/com/android/settings/fuelgauge/PowerUsageAdvanced.java rename to src/com/android/settings/fuelgauge/PowerUsageAdvancedLegacy.java index 2e94e2c74bc..a4e3fefeea7 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/PowerUsageAdvancedLegacy.java @@ -42,7 +42,7 @@ import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.datausage.DataUsageUtils; -import com.android.settings.fuelgauge.PowerUsageAdvanced.PowerUsageData.UsageType; +import com.android.settings.fuelgauge.PowerUsageAdvancedLegacy.PowerUsageData.UsageType; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; @@ -57,10 +57,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class PowerUsageAdvanced extends PowerUsageBase { +public class PowerUsageAdvancedLegacy extends PowerUsageBase { private static final String TAG = "AdvancedBatteryUsage"; - private static final String KEY_BATTERY_GRAPH = "battery_graph"; - private static final String KEY_BATTERY_USAGE_LIST = "battery_usage_list"; + private static final String KEY_BATTERY_GRAPH = "battery_graph_legacy"; + private static final String KEY_BATTERY_USAGE_LIST = "battery_usage_list_legacy"; private static final int STATUS_TYPE = BatteryStats.STATS_SINCE_CHARGED; @VisibleForTesting @@ -163,7 +163,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { @Override protected int getPreferenceScreenResId() { - return R.xml.power_usage_advanced; + return R.xml.power_usage_advanced_legacy; } @Override @@ -477,7 +477,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { public List getXmlResourcesToIndex( Context context, boolean enabled) { final SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = R.xml.power_usage_advanced; + sir.xmlResId = R.xml.power_usage_advanced_legacy; return Arrays.asList(sir); } }; diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java b/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java index 79425cb6b28..d321bb76f57 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java @@ -415,7 +415,7 @@ public class PowerUsageSummaryLegacy extends PowerUsageBase implements private void performBatteryHeaderClick() { if (mPowerFeatureProvider.isAdvancedUiEnabled()) { new SubSettingLauncher(getContext()) - .setDestination(PowerUsageAdvanced.class.getName()) + .setDestination(PowerUsageAdvancedLegacy.class.getName()) .setSourceMetricsCategory(getMetricsCategory()) .setTitle(R.string.advanced_battery_title) .launch(); diff --git a/src/com/android/settings/search/SearchIndexableResourcesImpl.java b/src/com/android/settings/search/SearchIndexableResourcesImpl.java index 87c2a9107b7..b9f4db2d017 100644 --- a/src/com/android/settings/search/SearchIndexableResourcesImpl.java +++ b/src/com/android/settings/search/SearchIndexableResourcesImpl.java @@ -49,8 +49,8 @@ import com.android.settings.display.NightDisplaySettings; import com.android.settings.display.ScreenZoomSettings; import com.android.settings.dream.DreamSettings; import com.android.settings.enterprise.EnterprisePrivacySettings; +import com.android.settings.fuelgauge.PowerUsageAdvancedLegacy; import com.android.settings.fuelgauge.batterysaver.BatterySaverSettings; -import com.android.settings.fuelgauge.PowerUsageAdvanced; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.fuelgauge.SmartBatterySettings; import com.android.settings.gestures.AssistGestureSettings; @@ -121,7 +121,7 @@ public class SearchIndexableResourcesImpl implements SearchIndexableResources { addIndex(SoundSettings.class); addIndex(ZenModeSettings.class); addIndex(StorageSettings.class); - addIndex(PowerUsageAdvanced.class); + addIndex(PowerUsageAdvancedLegacy.class); addIndex(DefaultAppSettings.class); addIndex(ManageAssist.class); addIndex(SpecialAccessSettings.class); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedLegacyTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java rename to tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedLegacyTest.java index 81d40a36ac4..756d9137e19 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedLegacyTest.java @@ -41,10 +41,10 @@ import com.android.internal.os.BatterySipper.DrainType; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; import com.android.settings.Utils; -import com.android.settings.fuelgauge.PowerUsageAdvanced.PowerUsageData; -import com.android.settings.fuelgauge.PowerUsageAdvanced.PowerUsageData.UsageType; import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.fuelgauge.PowerUsageAdvancedLegacy.PowerUsageData; +import com.android.settings.fuelgauge.PowerUsageAdvancedLegacy.PowerUsageData.UsageType; import org.junit.Before; import org.junit.Test; @@ -59,8 +59,7 @@ import java.util.Collections; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) -public class PowerUsageAdvancedTest { - +public class PowerUsageAdvancedLegacyTest { private static final int FAKE_UID_1 = 50; private static final int FAKE_UID_2 = 100; private static final int DISCHARGE_AMOUNT = 60; @@ -94,7 +93,7 @@ public class PowerUsageAdvancedTest { private UserInfo mNormalUserInfo; @Mock private UserInfo mManagedUserInfo; - private PowerUsageAdvanced mPowerUsageAdvanced; + private PowerUsageAdvancedLegacy mPowerUsageAdvanced; private PowerUsageData mPowerUsageData; private Context mShadowContext; private Intent mDischargingBatteryIntent; @@ -103,7 +102,7 @@ public class PowerUsageAdvancedTest { public void setUp() { MockitoAnnotations.initMocks(this); mShadowContext = spy(RuntimeEnvironment.application); - mPowerUsageAdvanced = spy(new PowerUsageAdvanced()); + mPowerUsageAdvanced = spy(new PowerUsageAdvancedLegacy()); List batterySippers = new ArrayList<>(); batterySippers.add(new BatterySipper(DrainType.APP, From 31a59604243f19f14d852f8cb54a3a1febf9f051 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 27 Feb 2018 14:30:57 -0800 Subject: [PATCH 15/17] Create new PowerUsageAdvanced page This page contains the graph at the top and app list at the bottom In this cl, we reused some part of the code in PowerUsageAdvancedLegacy to construct the graph, and also used controller to construct the app list(So glad that we extracted it to controller before..) Bug: 73300636 Test: RunSettingsRoboTests Change-Id: I30afbc0327a5186f8280f5e3678330b363e2dc0d Merged-In: I30afbc0327a5186f8280f5e3678330b363e2dc0d --- res/xml/power_usage_advanced.xml | 31 +++ res/xml/power_usage_summary.xml | 4 - .../BatteryAppListPreferenceController.java | 13 +- .../fuelgauge/PowerUsageAdvanced.java | 176 ++++++++++++++++++ .../settings/fuelgauge/PowerUsageSummary.java | 9 - ...atteryAppListPreferenceControllerTest.java | 14 -- .../fuelgauge/PowerUsageAdvancedTest.java | 111 +++++++++++ 7 files changed, 327 insertions(+), 31 deletions(-) create mode 100644 res/xml/power_usage_advanced.xml create mode 100644 src/com/android/settings/fuelgauge/PowerUsageAdvanced.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml new file mode 100644 index 00000000000..d32611ce969 --- /dev/null +++ b/res/xml/power_usage_advanced.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index d89653ae5ce..ac96151557e 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -66,8 +66,4 @@ - - diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java index 3ba5ee47fc0..5028264b596 100644 --- a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -149,7 +149,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro @Override public boolean isAvailable() { - return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST); + return true; } @Override @@ -186,12 +186,17 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro } } - public void refreshAppListGroup(BatteryStatsHelper statsHelper, boolean showAllApps, - CharSequence timeSequence) { + public void refreshAppListGroup(BatteryStatsHelper statsHelper, boolean showAllApps) { if (!isAvailable()) { return; } + mBatteryStatsHelper = statsHelper; + final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime( + mBatteryStatsHelper, System.currentTimeMillis()); + final CharSequence timeSequence = StringUtil.formatRelativeTime(mContext, + lastFullChargeTime, + false); final int resId = showAllApps ? R.string.power_usage_list_summary_device : R.string.power_usage_list_summary; mAppListGroup.setTitle(TextUtils.expandTemplate(mContext.getText(resId), timeSequence)); @@ -361,7 +366,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro final long usageTimeMs = sipper.usageTimeMs; if (usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) { final CharSequence timeSequence = - StringUtil.formatElapsedTime(mContext, usageTimeMs, false); + StringUtil.formatElapsedTime(mContext, usageTimeMs, false); preference.setSummary( (sipper.drainType != DrainType.APP || mBatteryUtils.shouldHideSipper(sipper)) ? timeSequence diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java new file mode 100644 index 00000000000..89f66bd8806 --- /dev/null +++ b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.android.settings.fuelgauge; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Bundle; +import android.provider.SearchIndexableResource; +import android.support.annotation.VisibleForTesting; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.utils.StringUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PowerUsageAdvanced extends PowerUsageBase { + private static final String TAG = "AdvancedBatteryUsage"; + private static final String KEY_BATTERY_GRAPH = "battery_graph"; + private static final String KEY_APP_LIST = "app_list"; + private static final String KEY_SHOW_ALL_APPS = "show_all_apps"; + @VisibleForTesting + static final int MENU_TOGGLE_APPS = Menu.FIRST + 1; + + @VisibleForTesting + BatteryHistoryPreference mHistPref; + private BatteryUtils mBatteryUtils; + private PowerUsageFeatureProvider mPowerUsageFeatureProvider; + private BatteryAppListPreferenceController mBatteryAppListPreferenceController; + @VisibleForTesting + boolean mShowAllApps = false; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + final Context context = getContext(); + + mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_GRAPH); + mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context); + mBatteryUtils = BatteryUtils.getInstance(context); + + // init the summary so other preferences won't have unnecessary move + updateHistPrefSummary(context); + restoreSavedInstance(icicle); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (getActivity().isChangingConfigurations()) { + BatteryEntry.clearUidCache(); + } + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.FUELGAUGE_BATTERY_HISTORY_DETAIL; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.power_usage_advanced; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE, + mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_TOGGLE_APPS: + mShowAllApps = !mShowAllApps; + item.setTitle(mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps); + mMetricsFeatureProvider.action(getContext(), + MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, + mShowAllApps); + restartBatteryStatsLoader(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @VisibleForTesting + void restoreSavedInstance(Bundle savedInstance) { + if (savedInstance != null) { + mShowAllApps = savedInstance.getBoolean(KEY_SHOW_ALL_APPS, false); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_SHOW_ALL_APPS, mShowAllApps); + } + + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + + mBatteryAppListPreferenceController = new BatteryAppListPreferenceController(context, + KEY_APP_LIST, getLifecycle(), (SettingsActivity) getActivity(), this); + controllers.add(mBatteryAppListPreferenceController); + + return controllers; + } + + @Override + protected void refreshUi() { + final Context context = getContext(); + if (context == null) { + return; + } + updatePreference(mHistPref); + updateHistPrefSummary(context); + + mBatteryAppListPreferenceController.refreshAppListGroup(mStatsHelper, mShowAllApps); + } + + private void updateHistPrefSummary(Context context) { + Intent batteryIntent = + context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + final boolean plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0; + + if (mPowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(context) && !plugged) { + mHistPref.setBottomSummary( + mPowerUsageFeatureProvider.getAdvancedUsageScreenInfoString()); + } else { + mHistPref.hideBottomSummary(); + } + } + + 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.power_usage_advanced; + return Arrays.asList(sir); + } + }; + +} diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index bf0b627f910..8d70e46463f 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -101,7 +101,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList SparseArray> mAnomalySparseArray; @VisibleForTesting BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; - private BatteryAppListPreferenceController mBatteryAppListPreferenceController; private BatteryTipPreferenceController mBatteryTipPreferenceController; private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; @@ -231,9 +230,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList mBatteryHeaderPreferenceController = new BatteryHeaderPreferenceController( context, activity, this /* host */, lifecycle); controllers.add(mBatteryHeaderPreferenceController); - mBatteryAppListPreferenceController = new BatteryAppListPreferenceController(context, - KEY_APP_LIST, lifecycle, activity, this); - controllers.add(mBatteryAppListPreferenceController); mBatteryTipPreferenceController = new BatteryTipPreferenceController(context, KEY_BATTERY_TIP, (SettingsActivity) getActivity(), this /* fragment */, this /* BatteryTipListener */); @@ -294,11 +290,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList updateLastFullChargePreference(lastFullChargeTime); mScreenUsagePref.setSubtitle(StringUtil.formatElapsedTime(getContext(), mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false)); - - final CharSequence timeSequence = StringUtil.formatRelativeTime(context, lastFullChargeTime, - false); - mBatteryAppListPreferenceController.refreshAppListGroup(mStatsHelper, - false /* showAllApps */, timeSequence); } @VisibleForTesting diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java index b223a10ed21..8156428d136 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java @@ -202,20 +202,6 @@ public class BatteryAppListPreferenceControllerTest { assertThat(mPreferenceController.shouldHideSipper(mNormalBatterySipper)).isFalse(); } - @Test - public void testIsAvailable_featureOn_returnTrue() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST, true); - - assertThat(mPreferenceController.isAvailable()).isTrue(); - } - - @Test - public void testIsAvailable_featureOff_returnFalse() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST, false); - - assertThat(mPreferenceController.isAvailable()).isFalse(); - } - @Test public void testNeverUseFakeData() { assertThat(BatteryAppListPreferenceController.USE_FAKE_DATA).isFalse(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java new file mode 100644 index 00000000000..eba62522b80 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Matchers.eq; +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.content.Context; +import android.os.Bundle; +import android.support.v7.preference.PreferenceScreen; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +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.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PowerUsageAdvancedTest { + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Menu mMenu; + @Mock + private MenuInflater mMenuInflater; + @Mock + private MenuItem mToggleAppsMenu; + private Context mContext; + private PowerUsageAdvanced mFragment; + private FakeFeatureFactory mFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mToggleAppsMenu.getItemId()).thenReturn(PowerUsageAdvanced.MENU_TOGGLE_APPS); + + mFragment = spy(new PowerUsageAdvanced()); + mFragment.onAttach(mContext); + } + + @Test + public void testSaveInstanceState_showAllAppsRestored() { + Bundle bundle = new Bundle(); + mFragment.mShowAllApps = true; + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + + mFragment.onSaveInstanceState(bundle); + mFragment.restoreSavedInstance(bundle); + + assertThat(mFragment.mShowAllApps).isTrue(); + } + + @Test + public void testOptionsMenu_menuAppToggle_metricEventInvoked() { + mFragment.mShowAllApps = false; + doNothing().when(mFragment).restartBatteryStatsLoader(); + + mFragment.onOptionsItemSelected(mToggleAppsMenu); + + verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), + eq(MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE), eq(true)); + } + + @Test + public void testOptionsMenu_toggleAppsEnabled() { + when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled()) + .thenReturn(true); + mFragment.mShowAllApps = false; + + mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); + + verify(mMenu).add(Menu.NONE, PowerUsageAdvanced.MENU_TOGGLE_APPS, Menu.NONE, + R.string.show_all_apps); + } +} From 094278e6363560ad620353b3431f476a42869f80 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 27 Feb 2018 14:58:06 -0800 Subject: [PATCH 16/17] Hook up the new page to PowerUsageSummary Add a menu to point to the new page Bug: 73300636 Test: RunSettingsRoboTests Change-Id: I9956e349cdbbe2cd752bdf65020a540a329bdee3 Merged-In: I9956e349cdbbe2cd752bdf65020a540a329bdee3 --- .../BatteryAppListPreferenceController.java | 3 +- .../fuelgauge/PowerUsageAdvanced.java | 10 +++++ .../settings/fuelgauge/PowerUsageSummary.java | 22 ++++++---- .../search/SearchIndexableResourcesImpl.java | 2 + .../fuelgauge/PowerUsageAdvancedTest.java | 3 -- .../fuelgauge/PowerUsageSummaryTest.java | 44 ++++++++++++++++++- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java index 5028264b596..de01533272c 100644 --- a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -22,6 +22,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.os.BatteryStats; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.UserHandle; @@ -81,7 +82,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro private Context mPrefContext; SparseArray> mAnomalySparseArray; - private Handler mHandler = new Handler() { + private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java index 89f66bd8806..327a6c58bfa 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java @@ -171,6 +171,16 @@ public class PowerUsageAdvanced extends PowerUsageBase { sir.xmlResId = R.xml.power_usage_advanced; return Arrays.asList(sir); } + + @Override + public List createPreferenceControllers( + Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new BatteryAppListPreferenceController(context, + KEY_APP_LIST, null /* lifecycle */, null /* activity */, + null /* fragment */)); + return controllers; + } }; } diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 8d70e46463f..b64dc526bad 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -39,6 +39,7 @@ 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.core.SubSettingLauncher; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.display.BatteryPercentagePreferenceController; import com.android.settings.fuelgauge.anomaly.Anomaly; @@ -49,7 +50,6 @@ import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.utils.PowerUtil; import com.android.settingslib.utils.StringUtil; @@ -68,7 +68,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList static final String TAG = "PowerUsageSummary"; private static final boolean DEBUG = false; - private static final String KEY_APP_LIST = "app_list"; private static final String KEY_BATTERY_HEADER = "battery_header"; private static final String KEY_BATTERY_TIP = "battery_tip"; @@ -80,7 +79,10 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList static final int BATTERY_INFO_LOADER = 1; @VisibleForTesting static final int BATTERY_TIP_LOADER = 2; - private static final int MENU_STATS_TYPE = Menu.FIRST; + @VisibleForTesting + static final int MENU_STATS_TYPE = Menu.FIRST; + @VisibleForTesting + static final int MENU_ADVANCED_BATTERY = Menu.FIRST + 1; public static final int DEBUG_INFO_LOADER = 3; @VisibleForTesting @@ -246,6 +248,8 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList .setAlphabeticShortcut('t'); } + menu.add(Menu.NONE, MENU_ADVANCED_BATTERY, Menu.NONE, R.string.advanced_battery_title); + super.onCreateOptionsMenu(menu, inflater); } @@ -256,11 +260,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList @Override public boolean onOptionsItemSelected(MenuItem item) { - final SettingsActivity sa = (SettingsActivity) getActivity(); - final Context context = getContext(); - final MetricsFeatureProvider metricsFeatureProvider = - FeatureFactory.getFactory(context).getMetricsFeatureProvider(); - switch (item.getItemId()) { case MENU_STATS_TYPE: if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) { @@ -270,6 +269,13 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList } refreshUi(); return true; + case MENU_ADVANCED_BATTERY: + new SubSettingLauncher(getContext()) + .setDestination(PowerUsageAdvanced.class.getName()) + .setSourceMetricsCategory(getMetricsCategory()) + .setTitle(R.string.advanced_battery_title) + .launch(); + return true; default: return super.onOptionsItemSelected(item); } diff --git a/src/com/android/settings/search/SearchIndexableResourcesImpl.java b/src/com/android/settings/search/SearchIndexableResourcesImpl.java index b9f4db2d017..1798d34e303 100644 --- a/src/com/android/settings/search/SearchIndexableResourcesImpl.java +++ b/src/com/android/settings/search/SearchIndexableResourcesImpl.java @@ -49,6 +49,7 @@ import com.android.settings.display.NightDisplaySettings; import com.android.settings.display.ScreenZoomSettings; import com.android.settings.dream.DreamSettings; import com.android.settings.enterprise.EnterprisePrivacySettings; +import com.android.settings.fuelgauge.PowerUsageAdvanced; import com.android.settings.fuelgauge.PowerUsageAdvancedLegacy; import com.android.settings.fuelgauge.batterysaver.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; @@ -121,6 +122,7 @@ public class SearchIndexableResourcesImpl implements SearchIndexableResources { addIndex(SoundSettings.class); addIndex(ZenModeSettings.class); addIndex(StorageSettings.class); + addIndex(PowerUsageAdvanced.class); addIndex(PowerUsageAdvancedLegacy.class); addIndex(DefaultAppSettings.class); addIndex(ManageAssist.class); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java index eba62522b80..4a905b4603a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java @@ -34,7 +34,6 @@ import android.view.MenuItem; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; -import com.android.settings.TestConfig; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -45,10 +44,8 @@ import org.mockito.Answers; 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 PowerUsageAdvancedTest { @Mock private PreferenceScreen mPreferenceScreen; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index 7f6e39d4096..6176bef4544 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -15,6 +15,8 @@ */ package com.android.settings.fuelgauge; +import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADVANCED_BATTERY; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; @@ -35,6 +37,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.SparseArray; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.TextView; @@ -53,7 +57,6 @@ import com.android.settingslib.core.AbstractPreferenceController; import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -114,6 +117,12 @@ public class PowerUsageSummaryTest { private LoaderManager mLoaderManager; @Mock private BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Menu mMenu; + @Mock + private MenuInflater mMenuInflater; + @Mock + private MenuItem mAdvancedPageMenu; private List mUsageList; private Context mRealContext; @@ -122,12 +131,13 @@ public class PowerUsageSummaryTest { private BatteryMeterView mBatteryMeterView; private PowerGaugePreference mScreenUsagePref; private PowerGaugePreference mLastFullChargePref; + private Intent mIntent; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mRealContext = RuntimeEnvironment.application; + mRealContext = spy(RuntimeEnvironment.application); mFeatureFactory = FakeFeatureFactory.setupForTest(); mScreenUsagePref = new PowerGaugePreference(mRealContext); mLastFullChargePref = new PowerGaugePreference(mRealContext); @@ -137,6 +147,7 @@ public class PowerUsageSummaryTest { mBatteryMeterView.mDrawable = new BatteryMeterView.BatteryMeterDrawable(mRealContext, 0); doNothing().when(mFragment).restartBatteryStatsLoader(); doReturn(mock(LoaderManager.class)).when(mFragment).getLoaderManager(); + doReturn(MENU_ADVANCED_BATTERY).when(mAdvancedPageMenu).getItemId(); when(mFragment.getActivity()).thenReturn(mSettingsActivity); when(mFeatureFactory.powerUsageFeatureProvider.getAdditionalBatteryInfoIntent()) @@ -294,6 +305,35 @@ public class PowerUsageSummaryTest { verify(mBatteryHeaderPreferenceController, never()).quickUpdateHeaderPreference(); } + @Test + public void testOptionsMenu_advancedPageEnabled() { + when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled()) + .thenReturn(true); + + mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); + + verify(mMenu).add(Menu.NONE, MENU_ADVANCED_BATTERY, Menu.NONE, + R.string.advanced_battery_title); + } + + @Test + public void testOptionsMenu_clickAdvancedPage_fireIntent() { + final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + doAnswer(invocation -> { + // Get the intent in which it has the app info bundle + mIntent = captor.getValue(); + return true; + }).when(mRealContext).startActivity(captor.capture()); + + mFragment.onOptionsItemSelected(mAdvancedPageMenu); + + assertThat(mIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo( + PowerUsageAdvanced.class.getName()); + assertThat( + mIntent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0)).isEqualTo( + R.string.advanced_battery_title); + } + public static class TestFragment extends PowerUsageSummary { private Context mContext; From 447192f903f85df1cf97352f81af46212302ec80 Mon Sep 17 00:00:00 2001 From: Yoshinori Hirano Date: Mon, 30 Oct 2017 14:38:57 +0900 Subject: [PATCH 17/17] Add SettingsMultiSelectListPreference style There is no OK and CANCEL button when changing APN Bearer. To fix this issue, OK and CANCEL button should be added to SettingsMultiSelectListPreference style. Fixes: 66928311 Test: manual - Add a new APN and tap Bearer Change-Id: I2b5719a7c1fdb721b15308e0eef7db3e6398e097 --- res/values/styles_preference.xml | 5 +++++ res/xml/apn_editor.xml | 1 + 2 files changed, 6 insertions(+) diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml index 5fe1ef41787..6b632ca7b1d 100644 --- a/res/values/styles_preference.xml +++ b/res/values/styles_preference.xml @@ -40,4 +40,9 @@ @layout/two_state_button + + \ No newline at end of file diff --git a/res/xml/apn_editor.xml b/res/xml/apn_editor.xml index b1e332bd7c4..742a436019e 100644 --- a/res/xml/apn_editor.xml +++ b/res/xml/apn_editor.xml @@ -157,6 +157,7 @@ android:entries="@array/bearer_entries" android:entryValues="@array/bearer_values" android:persistent="false" + style="@style/SettingsMultiSelectListPreference" />