Merge "Add an activity that starts DataUsageList fragment for mobile."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 66850ce..b3c72a8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2769,6 +2769,8 @@
             </intent-filter>
             <meta-data android:name="com.android.settings.category"
                 android:value="com.android.settings.category.ia.wireless" />
+            <meta-data android:name="com.android.settings.summary"
+                android:resource="@string/summary_empty"/>
             <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                 android:value="com.android.settings.sim.SimSettings" />
         </activity>
diff --git a/res/layout/private_dns_mode_dialog.xml b/res/layout/private_dns_mode_dialog.xml
new file mode 100644
index 0000000..4347055
--- /dev/null
+++ b/res/layout/private_dns_mode_dialog.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<RadioGroup
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dip">
+
+    <RadioButton
+        android:id="@+id/private_dns_mode_off"
+        android:text="@string/private_dns_mode_off"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dip"
+        />
+
+    <RadioButton
+        android:id="@+id/private_dns_mode_opportunistic"
+        android:text="@string/private_dns_mode_opportunistic"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dip"
+        />
+
+    <RadioButton
+        android:id="@+id/private_dns_mode_provider"
+        android:text="@string/private_dns_mode_provider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dip"
+        />
+
+    <EditText
+        android:id="@+id/private_dns_mode_provider_hostname"
+        android:hint="@string/private_dns_mode_provider_hostname_hint"
+        style="@android:style/Widget.CompoundButton.RadioButton"
+        android:imeOptions="actionDone"
+        android:inputType="textFilter|textUri"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dip"
+        android:layout_marginEnd="8dip"
+        />
+
+</RadioGroup>
diff --git a/res/values-ar/arrays.xml b/res/values-ar/arrays.xml
index a31ead4..d8029fa 100644
--- a/res/values-ar/arrays.xml
+++ b/res/values-ar/arrays.xml
@@ -219,7 +219,7 @@
     <item msgid="7471182818083460781">"IS95A"</item>
   </string-array>
   <string-array name="mvno_type_entries">
-    <item msgid="4367119357633573465">"None"</item>
+    <item msgid="4367119357633573465">"بدون"</item>
     <item msgid="6062567900587138000">"SPN"</item>
     <item msgid="2454085083342423481">"IMSI"</item>
     <item msgid="2681427309183221543">"GID"</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 467ad12..c1c872e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6432,6 +6432,8 @@
     <string name="sim_calls_ask_first_prefs_title">Ask every time</string>
     <!-- When a SIM preference hasn't been selected yet, this string is displayed as the pref summary until the user chooses a SIM subscription from the preference list [CHAR LIMIT=50] -->
     <string name="sim_selection_required_pref">Selection required</string>
+    <!-- Title for SIM selection notification channel -->
+    <string name="sim_selection_channel_title">SIM selection</string>
 
     <!--Dashboard strings-->
     <!-- Text to describe the dashboard fragment title [CHAR LIMIT=16] -->
diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml
index 7c0c0b3..2b4a85a 100644
--- a/res/xml/development_prefs.xml
+++ b/res/xml/development_prefs.xml
@@ -257,10 +257,13 @@
             android:entries="@array/bluetooth_a2dp_codec_ldac_playback_quality_titles"
             android:entryValues="@array/bluetooth_a2dp_codec_ldac_playback_quality_values" />
 
-        <SwitchPreference
-            android:key="dns_tls"
-            android:title="@string/dns_tls"
-            android:summary="@string/dns_tls_summary" />
+        <com.android.settings.development.PrivateDnsModeDialogPreference
+            android:key="select_private_dns_configuration"
+            android:title="@string/select_private_dns_configuration_title"
+            android:dialogTitle="@string/select_private_dns_configuration_dialog_title"
+            android:dialogLayout="@layout/private_dns_mode_dialog"
+            android:positiveButtonText="@string/save"
+            android:negativeButtonText="@string/cancel" />
 
     </PreferenceCategory>
 
diff --git a/src/com/android/settings/PrivacySettings.java b/src/com/android/settings/PrivacySettings.java
index a44e182..e547570 100644
--- a/src/com/android/settings/PrivacySettings.java
+++ b/src/com/android/settings/PrivacySettings.java
@@ -63,7 +63,6 @@
     @VisibleForTesting
     static final String DATA_MANAGEMENT = "data_management";
     private static final String BACKUP_INACTIVE = "backup_inactive";
-    private static final String FACTORY_RESET = "factory_reset";
     private static final String TAG = "PrivacySettings";
     private IBackupManager mBackupManager;
     private Preference mBackup;
@@ -245,9 +244,5 @@
             nonVisibleKeys.add(AUTO_RESTORE);
             nonVisibleKeys.add(CONFIGURE_ACCOUNT);
         }
-        if (RestrictedLockUtils.hasBaseUserRestriction(context,
-                UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId())) {
-            nonVisibleKeys.add(FACTORY_RESET);
-        }
     }
 }
diff --git a/src/com/android/settings/datetime/ZonePicker.java b/src/com/android/settings/datetime/ZonePicker.java
index 2d58dd8..87f2b75 100644
--- a/src/com/android/settings/datetime/ZonePicker.java
+++ b/src/com/android/settings/datetime/ZonePicker.java
@@ -22,6 +22,7 @@
 import android.app.ListFragment;
 import android.content.Context;
 import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -38,6 +39,7 @@
 import com.android.settings.core.instrumentation.VisibilityLoggerMixin;
 import com.android.settingslib.datetime.ZoneGetter;
 
+import java.text.Collator;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -257,15 +259,21 @@
         mVisibilityLoggerMixin.onPause();
     }
 
-    private static class MyComparator implements Comparator<Map<?, ?>> {
+    @VisibleForTesting
+    static class MyComparator implements Comparator<Map<?, ?>> {
+        private final Collator mCollator;
         private String mSortingKey;
+        private boolean mSortedByName;
 
         public MyComparator(String sortingKey) {
+            mCollator = Collator.getInstance();
             mSortingKey = sortingKey;
+            mSortedByName = ZoneGetter.KEY_DISPLAY_LABEL.equals(sortingKey);
         }
 
         public void setSortingKey(String sortingKey) {
             mSortingKey = sortingKey;
+            mSortedByName = ZoneGetter.KEY_DISPLAY_LABEL.equals(sortingKey);
         }
 
         public int compare(Map<?, ?> map1, Map<?, ?> map2) {
@@ -282,7 +290,11 @@
                 return -1;
             }
 
-            return ((Comparable) value1).compareTo(value2);
+            if (mSortedByName) {
+                return mCollator.compare(value1, value2);
+            } else {
+                return ((Comparable) value1).compareTo(value2);
+            }
         }
 
         private boolean isComparable(Object value) {
diff --git a/src/com/android/settings/development/DevelopmentSettings.java b/src/com/android/settings/development/DevelopmentSettings.java
index c0792e7..23cf4cc 100644
--- a/src/com/android/settings/development/DevelopmentSettings.java
+++ b/src/com/android/settings/development/DevelopmentSettings.java
@@ -217,7 +217,7 @@
     private static final String BLUETOOTH_SELECT_A2DP_CHANNEL_MODE_KEY = "bluetooth_select_a2dp_channel_mode";
     private static final String BLUETOOTH_SELECT_A2DP_LDAC_PLAYBACK_QUALITY_KEY = "bluetooth_select_a2dp_ldac_playback_quality";
 
-    private static final String DNS_TLS_KEY = "dns_tls";
+    private static final String PRIVATE_DNS_PREF_KEY = "select_private_dns_configuration";
 
     private static final String INACTIVE_APPS_KEY = "inactive_apps";
 
@@ -295,8 +295,6 @@
     private ListPreference mBluetoothSelectA2dpChannelMode;
     private ListPreference mBluetoothSelectA2dpLdacPlaybackQuality;
 
-    private SwitchPreference mDnsTls;
-
     private SwitchPreference mOtaDisableAutomaticUpdate;
     private SwitchPreference mWifiAllowScansWithTraffic;
     private SwitchPreference mStrictMode;
@@ -511,7 +509,7 @@
         mBluetoothSelectA2dpLdacPlaybackQuality = addListPreference(BLUETOOTH_SELECT_A2DP_LDAC_PLAYBACK_QUALITY_KEY);
         initBluetoothConfigurationValues();
 
-        mDnsTls = findAndInitSwitchPref(DNS_TLS_KEY);
+        updatePrivateDnsSummary();
 
         mWindowAnimationScale = addListPreference(WINDOW_ANIMATION_SCALE_KEY);
         mTransitionAnimationScale = addListPreference(TRANSITION_ANIMATION_SCALE_KEY);
@@ -829,8 +827,7 @@
         updateBluetoothDisableAbsVolumeOptions();
         updateBluetoothEnableInbandRingingOptions();
         updateBluetoothA2dpConfigurationValues();
-        updateSwitchPreference(mDnsTls, Settings.Global.getInt(cr,
-                Settings.Global.DNS_TLS_DISABLED, 0) == 0);
+        updatePrivateDnsSummary();
     }
 
     private void resetDangerousOptions() {
@@ -2183,6 +2180,13 @@
         }
     }
 
+    private void updatePrivateDnsSummary() {
+        final String summary = PrivateDnsModeDialogPreference.getSummaryStringForModeFromSettings(
+                getActivity().getContentResolver(), getActivity().getResources());
+        final Preference pref = findPreference(PRIVATE_DNS_PREF_KEY);
+        pref.setSummary(summary);
+    }
+
     private void writeImmediatelyDestroyActivitiesOptions() {
         try {
             ActivityManager.getService().setAlwaysFinish(
@@ -2534,10 +2538,6 @@
             writeBluetoothDisableAbsVolumeOptions();
         } else if (preference == mBluetoothEnableInbandRinging) {
             writeBluetoothEnableInbandRingingOptions();
-        } else if (preference == mDnsTls) {
-            Settings.Global.putInt(getActivity().getContentResolver(),
-                    Settings.Global.DNS_TLS_DISABLED,
-                    mDnsTls.isChecked() ? 0 : 1);
         } else if (SHORTCUT_MANAGER_RESET_KEY.equals(preference.getKey())) {
             resetShortcutManagerThrottling();
         } else {
diff --git a/src/com/android/settings/development/PrivateDnsModeDialogPreference.java b/src/com/android/settings/development/PrivateDnsModeDialogPreference.java
new file mode 100644
index 0000000..ee44b08
--- /dev/null
+++ b/src/com/android/settings/development/PrivateDnsModeDialogPreference.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.development;
+
+import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v14.preference.PreferenceDialogFragment;
+import android.support.v7.preference.DialogPreference;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.settings.R;
+import com.android.settingslib.CustomDialogPreference;
+
+
+public class PrivateDnsModeDialogPreference extends CustomDialogPreference
+        implements OnCheckedChangeListener, TextWatcher, OnEditorActionListener {
+    private static final String TAG = PrivateDnsModeDialogPreference.class.getSimpleName();
+
+    private static final String MODE_KEY = Settings.Global.PRIVATE_DNS_MODE;
+    private static final String HOSTNAME_KEY = Settings.Global.PRIVATE_DNS_SPECIFIER;
+    private String mMode;
+    private EditText mEditText;
+
+    public static String getSummaryStringForModeFromSettings(ContentResolver cr, Resources res) {
+        final String mode = getModeFromSettings(cr);
+        switch (mode) {
+            case PRIVATE_DNS_MODE_OFF:
+                return res.getString(R.string.private_dns_mode_off);
+            case PRIVATE_DNS_MODE_OPPORTUNISTIC:
+                return res.getString(R.string.private_dns_mode_opportunistic);
+            case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
+                return getHostnameFromSettings(cr);
+            default:
+                return "unknown";
+        }
+    }
+
+    public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public PrivateDnsModeDialogPreference(Context context) {
+        super(context);
+    }
+
+    // This is called first when the dialog is launched.
+    @Override
+    protected void onBindDialogView(View view) {
+        final String mode = getModeFromSettings();
+
+        RadioButton rb = (RadioButton) view.findViewById(R.id.private_dns_mode_off);
+        if (mode.equals(PRIVATE_DNS_MODE_OFF)) rb.setChecked(true);
+        rb.setOnCheckedChangeListener(this);
+
+        rb = (RadioButton) view.findViewById(R.id.private_dns_mode_opportunistic);
+        if (mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC)) rb.setChecked(true);
+        rb.setOnCheckedChangeListener(this);
+
+        rb = (RadioButton) view.findViewById(R.id.private_dns_mode_provider);
+        if (mode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) rb.setChecked(true);
+        rb.setOnCheckedChangeListener(this);
+
+        mEditText = (EditText) view.findViewById(R.id.private_dns_mode_provider_hostname);
+        mEditText.setOnEditorActionListener(this);
+        mEditText.addTextChangedListener(this);
+
+        // (Mostly) Fix the EditText field's indentation to align underneath the
+        // displayed radio button text, and not under the radio button itself.
+        final int padding = rb.isLayoutRtl()
+                ? rb.getCompoundPaddingRight()
+                : rb.getCompoundPaddingLeft();
+        final MarginLayoutParams marginParams = (MarginLayoutParams) mEditText.getLayoutParams();
+        marginParams.setMarginStart(marginParams.getMarginStart() + padding);
+        mEditText.setLayoutParams(marginParams);
+        mEditText.setText(getHostnameFromSettings());
+
+        setDialogValue(mode);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (!positiveResult) return;
+
+        saveDialogValue();
+        setSummary(getSummaryStringForModeFromSettings(
+                getContext().getContentResolver(), getContext().getResources()));
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (!isChecked) return;
+
+        switch (buttonView.getId()) {
+            case R.id.private_dns_mode_off:
+                setDialogValue(PRIVATE_DNS_MODE_OFF);
+                break;
+            case R.id.private_dns_mode_opportunistic:
+                setDialogValue(PRIVATE_DNS_MODE_OPPORTUNISTIC);
+                break;
+            case R.id.private_dns_mode_provider:
+                setDialogValue(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+                break;
+            default:
+                // Unknown button; ignored.
+                break;
+        }
+    }
+
+    @Override
+    public boolean onEditorAction(TextView tv, int actionId, KeyEvent k) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            saveDialogValue();
+            getDialog().dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) { return; }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) { return; }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        final String hostname = s.toString();
+        final boolean appearsValid = isWeaklyValidatedHostname(hostname);
+        // TODO: Disable the "positive button" ("Save") when appearsValid is false.
+    }
+
+    private void setDialogValue(String mode) {
+        mMode = mode;
+        final boolean txtEnabled = mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+        mEditText.setEnabled(txtEnabled);
+    }
+
+    private void saveDialogValue() {
+        if (!isValidMode(mMode)) {
+            mMode = PRIVATE_DNS_DEFAULT_MODE;
+        }
+
+        if (mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
+            final String hostname = mEditText.getText().toString();
+            if (isWeaklyValidatedHostname(hostname)) {
+                saveHostnameToSettings(hostname);
+            } else {
+                // TODO: Once quasi-validation of hostnames works and acceptable
+                // user signaling is working, this can be deleted.
+                mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+                if (TextUtils.isEmpty(hostname)) saveHostnameToSettings("");
+            }
+        }
+
+        saveModeToSettings(mMode);
+    }
+
+    private String getModeFromSettings() {
+        return getModeFromSettings(getContext().getContentResolver());
+    }
+
+    private void saveModeToSettings(String value) {
+        Settings.Global.putString(getContext().getContentResolver(), MODE_KEY, value);
+    }
+
+    private String getHostnameFromSettings() {
+        return getHostnameFromSettings(getContext().getContentResolver());
+    }
+
+    private void saveHostnameToSettings(String hostname) {
+        Settings.Global.putString(getContext().getContentResolver(), HOSTNAME_KEY, hostname);
+    }
+
+    private static String getModeFromSettings(ContentResolver cr) {
+        final String mode = Settings.Global.getString(cr, MODE_KEY);
+        return isValidMode(mode) ? mode : PRIVATE_DNS_DEFAULT_MODE;
+    }
+
+    private static boolean isValidMode(String mode) {
+        return !TextUtils.isEmpty(mode) && (
+                mode.equals(PRIVATE_DNS_MODE_OFF) ||
+                mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
+                mode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME));
+    }
+
+    private static String getHostnameFromSettings(ContentResolver cr) {
+        return Settings.Global.getString(cr, HOSTNAME_KEY);
+    }
+
+    private static boolean isWeaklyValidatedHostname(String hostname) {
+        // TODO: Find and use a better validation method.  Specifically:
+        //     [1] this should reject IP string literals, and
+        //     [2] do the best, simplest, future-proof verification that
+        //         the input approximates a DNS hostname.
+        final String WEAK_HOSTNAME_REGEX = "^[a-zA-Z0-9_.-]+$";
+        return hostname.matches(WEAK_HOSTNAME_REGEX);
+    }
+}
diff --git a/src/com/android/settings/location/LocationSettingsBase.java b/src/com/android/settings/location/LocationSettingsBase.java
index 741d607..1ea21b6 100644
--- a/src/com/android/settings/location/LocationSettingsBase.java
+++ b/src/com/android/settings/location/LocationSettingsBase.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.location;
 
+import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,17 +29,14 @@
 
 import com.android.settings.SettingsPreferenceFragment;
 
+import static com.android.settingslib.Utils.updateLocationMode;
+
 /**
  * A base class that listens to location settings change and modifies location
  * settings.
  */
 public abstract class LocationSettingsBase extends SettingsPreferenceFragment {
     private static final String TAG = "LocationSettingsBase";
-    /** Broadcast intent action when the location mode is about to change. */
-    private static final String MODE_CHANGING_ACTION =
-            "com.android.settings.location.MODE_CHANGING";
-    private static final String CURRENT_MODE_KEY = "CURRENT_MODE";
-    private static final String NEW_MODE_KEY = "NEW_MODE";
 
     private int mCurrentMode;
     private BroadcastReceiver mReceiver;
@@ -90,15 +88,6 @@
         return um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION);
     }
 
-    public static boolean updateLocationMode(Context context, int oldMode, int newMode) {
-        Intent intent = new Intent(MODE_CHANGING_ACTION);
-        intent.putExtra(CURRENT_MODE_KEY, oldMode);
-        intent.putExtra(NEW_MODE_KEY, newMode);
-        context.sendBroadcast(intent, android.Manifest.permission.WRITE_SECURE_SETTINGS);
-        return Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE,
-                newMode);
-    }
-
     public void setLocationMode(int mode) {
         Context context = getActivity();
         if (isRestricted(context)) {
@@ -115,7 +104,7 @@
             return;
         }
 
-        updateLocationMode(context, mCurrentMode, mode);
+        updateLocationMode(context, mCurrentMode, mode, ActivityManager.getCurrentUser());
         refreshLocationMode();
     }
 
diff --git a/src/com/android/settings/sim/SimSelectNotification.java b/src/com/android/settings/sim/SimSelectNotification.java
index 67e423b..2dd2dcf 100644
--- a/src/com/android/settings/sim/SimSelectNotification.java
+++ b/src/com/android/settings/sim/SimSelectNotification.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.sim;
 
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -40,6 +41,9 @@
     private static final String TAG = "SimSelectNotification";
     private static final int NOTIFICATION_ID = 1;
 
+    private static final String SIM_SELECT_NOTIFICATION_CHANNEL =
+            "sim_select_notification_channel";
+
     @Override
     public void onReceive(Context context, Intent intent) {
         final TelephonyManager telephonyManager = (TelephonyManager)
@@ -117,8 +121,14 @@
 
     private void createNotification(Context context){
         final Resources resources = context.getResources();
+
+        NotificationChannel notificationChannel = new NotificationChannel(
+                SIM_SELECT_NOTIFICATION_CHANNEL,
+                resources.getString(R.string.sim_selection_channel_title),
+                NotificationManager.IMPORTANCE_LOW);
+
         NotificationCompat.Builder builder =
-                new NotificationCompat.Builder(context)
+                new NotificationCompat.Builder(context, SIM_SELECT_NOTIFICATION_CHANNEL)
                 .setSmallIcon(R.drawable.ic_sim_card_alert_white_48dp)
                 .setColor(context.getColor(R.color.sim_noitification))
                 .setContentTitle(resources.getString(R.string.sim_notification_title))
@@ -130,6 +140,7 @@
         builder.setContentIntent(resultPendingIntent);
         NotificationManager notificationManager =
                 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.createNotificationChannel(notificationChannel);
         notificationManager.notify(NOTIFICATION_ID, builder.build());
     }
 
diff --git a/src/com/android/settings/wfd/WifiDisplaySettings.java b/src/com/android/settings/wfd/WifiDisplaySettings.java
index 0d6b4dc..3fe438f 100755
--- a/src/com/android/settings/wfd/WifiDisplaySettings.java
+++ b/src/com/android/settings/wfd/WifiDisplaySettings.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.ListPreference;
@@ -212,8 +213,13 @@
     }
 
     public static boolean isAvailable(Context context) {
-        return context.getSystemService(Context.DISPLAY_SERVICE) != null
-                && context.getSystemService(Context.WIFI_P2P_SERVICE) != null;
+        try {
+            return context.getSystemService(Context.DISPLAY_SERVICE) != null
+                    && context.getSystemService(Context.WIFI_P2P_SERVICE) != null;
+        } catch (Exception e) {
+            // Service is not registered, so this is definitely not available.
+            return false;
+        }
     }
 
     private void scheduleUpdate(int changes) {
diff --git a/tests/robotests/src/com/android/settings/datetime/ZonePickerComparatorTest.java b/tests/robotests/src/com/android/settings/datetime/ZonePickerComparatorTest.java
new file mode 100644
index 0000000..4c1794c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/datetime/ZonePickerComparatorTest.java
@@ -0,0 +1,123 @@
+package com.android.settings.datetime;
+
+import com.android.settings.datetime.ZonePicker;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.datetime.ZoneGetter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ZonePickerComparatorTest {
+
+    // Strings in Chinese are sorted by alphabet order of their Pinyin.
+    // "伦敦" -> "lundun";  "纽约" -> "niuyue";  "悉尼" -> "xini"
+    // "开罗" -> "kailuo";  "雅典" -> "yadian";  "上海" -> "shanghai"
+    private static final String[] TEST_CHINESE_NAME =
+            new String[]{"伦敦", "纽约", "悉尼", "开罗", "雅典", "上海"};
+    private static final String[] ORDERED_CHINESE_NAME =
+            new String[]{"开罗", "伦敦", "纽约", "上海", "悉尼", "雅典"};
+
+    private static final String[] TEST_ENGLISH_NAME =
+            new String[]{"London", "New York", "Sydney", "Cairo", "Athens", "Shanghai"};
+    private static final String[] ORDERED_ENGLISH_NAME =
+            new String[]{"Athens", "Cairo", "London", "New York", "Shanghai", "Sydney"};
+
+    private static final Locale INIT_LOCALE = Locale.getDefault();
+
+    private Map<String, List> mTestDataMap;
+    private List<Map<String, Object>> mTestList;
+
+    @Before
+    public void setUp() {
+        mTestDataMap = new HashMap<>();
+        mTestDataMap.put("zh_CN", Arrays.asList(TEST_CHINESE_NAME));
+        mTestDataMap.put("en_US", Arrays.asList(TEST_ENGLISH_NAME));
+    }
+
+    @After
+    public void tearDown() {
+        Locale.setDefault(INIT_LOCALE);
+    }
+
+    @Test
+    public void testComparator_sortChineseString() {
+        String sortKey = ZoneGetter.KEY_DISPLAY_LABEL;
+        mTestList = getMockZonesList("zh_CN");
+        Locale.setDefault(new Locale("zh"));
+        final ZonePicker.MyComparator comparator = new ZonePicker.MyComparator(sortKey);
+        assertThat(comparator).isNotNull();
+        Collections.sort(mTestList, comparator);
+        for (int i = 0; i < mTestList.size(); i++) {
+            assertThat(mTestList.get(i).get(sortKey).toString())
+                    .isEqualTo(ORDERED_CHINESE_NAME[i]);
+        }
+    }
+
+    @Test
+    public void testComparator_sortEnglishString() {
+        String sortKey = ZoneGetter.KEY_DISPLAY_LABEL;
+        mTestList = getMockZonesList("en_US");
+        Locale.setDefault(new Locale("en"));
+        final ZonePicker.MyComparator comparator = new ZonePicker.MyComparator(sortKey);
+        assertThat(comparator).isNotNull();
+        Collections.sort(mTestList, comparator);
+        for (int i = 0; i < mTestList.size(); i++) {
+            assertThat(mTestList.get(i).get(sortKey).toString())
+                    .isEqualTo(ORDERED_ENGLISH_NAME[i]);
+        }
+    }
+
+    @Test
+    public void testComparator_sortInteger() {
+        String sortKey = ZoneGetter.KEY_OFFSET;
+        // TestList of any locale can be selected to test integer sorting.
+        mTestList = getMockZonesList("en_US");
+        final ZonePicker.MyComparator comparator = new ZonePicker.MyComparator(sortKey);
+        assertThat(comparator).isNotNull();
+        Collections.sort(mTestList, comparator);
+        for (int i = 0; i < mTestList.size(); i++) {
+            assertThat(mTestList.get(i).get(sortKey)).isEqualTo(i);
+        }
+    }
+
+    private List<Map<String, Object>> getMockZonesList(String locale) {
+         List<Map<String, Object>> zones = new ArrayList<>();
+         List<String> testData = mTestDataMap.get(locale);
+         TimeZone tz = TimeZone.getDefault();
+         int testSize = testData.size();
+         for (int i = 0; i < testSize; i++) {
+             zones.add(createMockDisplayEntry(tz, "GMT+08:00",
+                    testData.get(i), testSize - i - 1));
+         }
+         return zones;
+    }
+
+    private Map<String, Object> createMockDisplayEntry(
+            TimeZone tz, CharSequence gmtOffsetText, CharSequence displayName, int offsetMillis) {
+         Map<String, Object> map = new HashMap<>();
+         map.put(ZoneGetter.KEY_ID, tz.getID());
+         map.put(ZoneGetter.KEY_DISPLAYNAME, displayName.toString());
+         map.put(ZoneGetter.KEY_DISPLAY_LABEL, displayName);
+         map.put(ZoneGetter.KEY_GMT, gmtOffsetText.toString());
+         map.put(ZoneGetter.KEY_OFFSET_LABEL, gmtOffsetText);
+         map.put(ZoneGetter.KEY_OFFSET, offsetMillis);
+         return map;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wfd/WifiDisplaySettingsTest.java b/tests/robotests/src/com/android/settings/wfd/WifiDisplaySettingsTest.java
index df45ad5..7d8ee52 100644
--- a/tests/robotests/src/com/android/settings/wfd/WifiDisplaySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/wfd/WifiDisplaySettingsTest.java
@@ -16,16 +16,23 @@
 
 package com.android.settings.wfd;
 
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Activity;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.media.MediaRouter;
 import android.net.wifi.p2p.WifiP2pManager;
+import android.os.ServiceManager;
 
 import com.android.settings.R;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.dashboard.SummaryLoader;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -34,12 +41,6 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class WifiDisplaySettingsTest {
@@ -89,7 +90,15 @@
     public void isAvailable_noService_shouldReturnFalse() {
         assertThat(WifiDisplaySettings.isAvailable(mActivity))
                 .isFalse();
+    }
 
+    @Test
+    public void isAvailable_throwException_shouldReturnFalse() {
+        when(mActivity.getSystemService(Context.WIFI_P2P_SERVICE))
+                .thenThrow(new IllegalStateException());
+
+        assertThat(WifiDisplaySettings.isAvailable(mActivity))
+                .isFalse();
     }
 
     @Test